دليل شامل حول تعلُّم التعزيز الموزّع عالي الأداء على GKE Standard

1- مقدمة

يوضّح هذا المختبر بالتفصيل كيفية إنشاء حلقة تدريب موزّعة عالية الأداء للتعلم المعزّز (RL) وتوفيرها وتنفيذها على GKE Standard باستخدام GKE Agent Sandboxes (gVisor)، وذلك باستخدام خوارزمية Group Relative Policy Optimization (GRPO) مع مكتبة trl.

الهدف هو توضيح كيفية تقييم الرمز الذي تم إنشاؤه باستخدام نماذج اللغة الكبيرة (LLM) وغير الموثوق به بأمان أثناء حلقة تدريب التعلم المعزّز. نحقّق ذلك من خلال فصل مستوى التنسيق (Ray) عن مستوى التنفيذ (GKE Agent Sandboxes).

التحدي التقني لتقييم رمز التعلم المعزّز

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

  1. عنق الزجاجة في عملية تغيير مجموعة العناصر: تعمل أُطر التقييم التقليدية على تشغيل حاوية Docker جديدة لكل مهمة. يؤدي إجراء ذلك بشكلٍ ديناميكي لمئات عمليات النشر المتوازية أثناء حلقة تدريب التعلم المعزّز إلى حدوث حمولة كبيرة على مستوى التحكّم في Kubernetes. تؤدي فترة الانتظار إلى جعل تدريب التعلم المعزّز عالي التردد مستحيلاً.
  2. المخاطر الأمنية: يؤدي تشغيل رمز عشوائي تم إنشاؤه باستخدام نماذج اللغة الكبيرة داخل أوقات تشغيل الحاويات العادية إلى مشاركة نواة نظام التشغيل المضيف. يمكن أن يؤدي ثغرة أمنية واحدة إلى تعريض عُقدك للخطر.
  3. سرقة رموز إدارة الهوية وإمكانية الوصول (IAM): يمكن أن يستعلم الرمز الذي تم إنشاؤه باستخدام نماذج اللغة الكبيرة والذي يتم تشغيله داخل حاوية Kubernetes عن خادم البيانات الوصفية لمزوّد السحابة لسرقة رموز حسابات خدمة إدارة الهوية وإمكانية الوصول للعُقد.

الحل: فصل التنسيق عن التنفيذ

يفصل هذا البنية التنسيق عن التنفيذ:

  • المنسّق (Ray): تدير مجموعة Ray موزّعة حلقة تدريب التعلم المعزّز وتوزّع عملية إنشاء عمليات النشر.
  • مستوى التنفيذ (GKE Agent Sandbox): بدلاً من إنشاء حاويات Kubernetes بشكلٍ ديناميكي، يُجري العاملون في Ray طلبات HTTP بسيطة إلى جهاز توجيه وضع الحماية مخصّص. يُخصّص جهاز التوجيه على الفور للعامل حاوية معزولة تم تسخينها مسبقًا وتعمل ضمن gVisor (GKE Sandbox).
  • فترة انتظار أقل من ثانية: بما أنّ عمليات وضع الحماية يتم تسخينها مسبقًا في SandboxWarmPool مُدارة وتتم إدارتها من خلال بوابة HTTP عالية السرعة، ينخفض وقت إنشاء البيئة إلى أقل من 200 ملّي ثانية، ما يؤدي إلى تجاوز مستوى التحكّم في Kubernetes بالكامل.

أهداف المختبر

في هذا الدرس التطبيقي حول الترميز، ستتعرّف على ما يلي:

  • التحديات والحلول المعمارية لتقييم الرمز غير الموثوق به في حلقات التعلم المعزّز.
  • كيفية إنشاء صور مخصّصة لوضع الحماية لعمليات النشر الفعّالة.
  • كيفية إعداد GKE Agent Sandboxes وSandboxWarmPools واستخدامها.
  • كيفية عزل عمليات وضع الحماية بشكلٍ آمن لمنع سرقة رموز إدارة الهوية وإمكانية الوصول.
  • كيفية تشغيل مهمة تدريب أساسية للتعلم المعزّز باستخدام SweBench وTRL باستخدام Ray لفصل التنسيق عن التنفيذ.

2. إنشاء المجموعة والمتطلبات الأساسية

قبل المتابعة، تحتاج إلى مجموعة GKE تتضمّن مجموعة أجهزة ذات التخصيص نفسه لوحدة معالجة رسومات عالية الأداء وRay Operator مثبَّتًا لإدارة حجم المعالجة للتدريب.

المتطلبات الأساسية

يفترض هذا الدرس التطبيقي حول الترميز أنّ الأدوات التالية مثبَّتة ومُعدّة:

المتغيرات البيئية

أولاً، اضبط المتغيرات البيئية التي سيتم استخدامها في هذا الدرس التطبيقي حول الترميز. تستخدم الأوامر أدناه إعدادات تلقائية مناسبة، ولكن يمكنك تغييرها حسب الحاجة لتتطابق مع بيئة Google Cloud المحدّدة:

export PROJECT_ID=$(gcloud config get-value project)
export REGION="us-west3"
export ZONE="us-west3-a"
export REPO_NAME="rl-sandbox-repo"

أنشئ مستودعًا في Artifact Registry لتخزين صور الحاويات المخصّصة:

gcloud artifacts repositories create $REPO_NAME \
    --repository-format=docker \
    --location=$REGION \
    --description="Repository for RL Sandbox images"

إعدادات المجموعة

للحصول على شرح تفصيلي حول توفير مجموعة GKE محسّنة لأحمال عمل الذكاء الاصطناعي (بما في ذلك توصيل شبكة GPUDirect RDMA)، اتّبِع المستندات الرسمية: إنشاء مجموعة مخصّصة للحوسبة الفائقة للذكاء الاصطناعي في GKE

المتطلبات الأساسية البالغة الأهمية: عند إنشاء مجموعتك أو مجموعة أجهزة ذات التخصيص نفسه لعُقد تنفيذ معيّنة، تأكَّد من تمرير علامتَي --enable-agent-sandbox و--sandbox type=gvisor لتثبيت تعريفات الموارد المخصّصة (CRD) المطلوبة لمجموعات وضع الحماية التي تم تسخينها مسبقًا.

بافتراض أنّ مجموعتك ووحدات معالجة الرسومات وRay Operator قيد التشغيل، يوضّح كل ما يلي كيفية إعداد مستوى التنفيذ وتشغيل حلقة التعلم المعزّز.

3. إنشاء صور مخصّصة

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

1- إنشاء صورة العامل في وحدة معالجة الرسومات

يحتاج العامل في وحدة معالجة الرسومات في Ray إلى مكتبات لتشغيل نموذج اللغة وتنسيق حلقة التدريب. ننشئ هذه الصورة استنادًا إلى صورة vLLM الرسمية حتى تتوافق مع أحدث وحدات معالجة الرسومات ويكون PyTorch/CUDA مثبَّتًا مسبقًا.

شغِّل الأمر التالي لإنشاء Dockerfile.gpu_worker:

cat << 'EOF' > Dockerfile.gpu_worker
# ==============================================================================
# Base Image: Use the official vLLM production image. 
# This image comes pre-baked with PyTorch 2.11, CUDA 13.0, and vLLM.
# It supports sm_100 Blackwell GPUs natively!
# ==============================================================================
FROM vllm/vllm-openai:latest

USER root

# Install system dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
    build-essential \
    numactl \
    libnuma-dev \
    wget \
    ca-certificates \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*

# Install Ray, TRL, and Sandbox tools
# TRL does not require compiling flash_attn from source.
RUN pip install --no-cache-dir \
    "ray[default]==2.55.1" \
    "numpy<2.0" \
    gymnasium>=0.28.1 \
    k8s-agent-sandbox>=0.4.6 \
    trl transformers packaging ninja cachetools accelerate datasets peft
EOF

أنشئ الصورة وادفعها إلى مستودع Artifact Registry:

export WORKER_REPO="${REGION}-docker.pkg.dev/${PROJECT_ID}/${REPO_NAME}/ray-gpu-worker:v1"
docker build -f Dockerfile.gpu_worker -t $WORKER_REPO .
docker push $WORKER_REPO

ملاحظة: يستخدم هذا الدليل أوامر docker محلية لإنشاء الصور. إذا كنت تفضّل إنشاء الصور عن بُعد، يمكنك استخدام Cloud Build بدلاً من ذلك (على سبيل المثال، باستخدام gcloud builds submit).

2- إنشاء صورة العُقدة الرئيسية لوحدة المعالجة المركزية

تنسّق العُقدة الرئيسية في Ray المجموعة فقط ولا تشغّل نماذج التدريب الثقيلة لوحدة معالجة الرسومات. لتجنُّب عنق الزجاجة الهائل في عملية سحب الصور (عادةً ما تكون 15 غيغابايت أو أكثر) على عُقد وحدة المعالجة المركزية العادية، ننشئ صورة خفيفة الوزن لوحدة المعالجة المركزية فقط للعُقدة الرئيسية. تحتوي هذه الصورة على Ray ومكتبات Python المطلوبة، ولكنها لا تتضمّن مكتبات وحدة معالجة الرسومات الثقيلة، مثل CUDA وvLLM.

شغِّل الأمر التالي لإنشاء Dockerfile.head:

cat << 'EOF' > Dockerfile.head
# ==============================================================================
# Base Image: Use the official Python slim image for the exact patch version.
# This aligns the Python version (3.12.13) with the GPU worker node.
# ==============================================================================
FROM python:3.12.13-slim

USER root

# Install system dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
    build-essential \
    wget \
    ca-certificates \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*

# Install Ray, TRL, and Sandbox tools (CPU versions where applicable)
# We install torch CPU first to avoid pulling the 2GB+ CUDA torch package.
RUN pip install --no-cache-dir torch --index-url https://download.pytorch.org/whl/cpu && \
    pip install --no-cache-dir \
    "ray[default]==2.55.1" \
    "numpy<2.0" \
    gymnasium>=0.28.1 \
    k8s-agent-sandbox>=0.4.6 \
    trl transformers packaging ninja cachetools accelerate datasets peft

# Create a 'ray' user to run the container securely and match Ray conventions
RUN useradd -ms /bin/bash ray
USER ray
WORKDIR /home/ray
EOF

أنشئ الصورة وادفعها:

export HEAD_REPO="${REGION}-docker.pkg.dev/${PROJECT_ID}/${REPO_NAME}/ray-head:v1"
docker build -f Dockerfile.head -t $HEAD_REPO .
docker push $HEAD_REPO

3- إنشاء صورة وضع الحماية

تحتاج وضع الحماية إلى التبعيات المحدّدة للمهمة التي نقيّمها حتى يكون التثبيت في وقت التشغيل فوريًا. في هذا الدرس التطبيقي حول الترميز، سنستخدم مشكلة من مستودع django/django في SWE-bench. سنستنسخ المستودع مسبقًا وننشئ بيئات Python مسبقًا حتى لا تضيّع النصوص البرمجية للنموذج وقتًا في تنزيلها في حلقة التعلم المعزّز.

شغِّل الأمر التالي لإنشاء Dockerfile.sandbox:

cat << 'EOF' > Dockerfile.sandbox
# Use a stable Debian-based Miniconda image
FROM condaforge/miniforge3:latest

# 1. Install essential system libraries (including sqlite3 for Django tests)
RUN apt-get update && apt-get install -y \
    git \
    build-essential \
    libsqlite3-dev \
    && rm -rf /var/lib/apt/lists/*

# 2. Set up the /workspace directory and grant ownership to the pre-existing non-root 'ubuntu' user (UID 1000)
RUN mkdir -p /workspace \
    && chown -R 1000:1000 /workspace

# 3. Switch to the non-root user
USER ubuntu
WORKDIR /workspace

# 4. Pre-configure Git globally so the agent can run git commands
RUN git config --global user.email "agent@gke-sandbox.local" \
    && git config --global user.name "Agent"

# 5. Pre-clone the repository as the non-root user
RUN git clone https://github.com/django/django.git .

# 6. Pre-build Conda environments and pre-cache common dependencies
# We do NOT run "pip install -e ." here to avoid Python version conflicts with the main branch.
# Instead, we pre-install the heavy dependencies so that runtime installation is instantaneous.
RUN conda create -y -n django-py39 python=3.9 \
    && conda run -n django-py39 pip install --no-cache-dir asgiref sqlparse tzdata pytest pytest-django

RUN conda create -y -n django-py310 python=3.10 \
    && conda run -n django-py310 pip install --no-cache-dir asgiref sqlparse tzdata pytest pytest-django

# --- Add Agent Server ---
# We use a multi-stage build to copy the agent server from the official python-runtime-sandbox image
COPY --from=registry.k8s.io/agent-sandbox/python-runtime-sandbox:v0.1.0 /app /opt/sandbox-agent
USER root
RUN chown -R 1000:1000 /opt/sandbox-agent \
    && /opt/conda/bin/pip install --no-cache-dir -r /opt/sandbox-agent/requirements.txt \
    && sed -i 's|"/app"|"/workspace"|g' /opt/sandbox-agent/main.py
USER ubuntu
# ------------------------

# Prepend the django-py39 conda environment bin to PATH for commands executed inside the container
ENV PATH=/home/ubuntu/.conda/envs/django-py39/bin:$PATH

# Keep the container alive and run the agent server using the system Python
CMD ["/opt/conda/bin/python3", "-m", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8888", "--log-level", "trace", "--app-dir", "/opt/sandbox-agent"]
EOF

أنشئ الصورة وادفعها:

export SANDBOX_REPO="${REGION}-docker.pkg.dev/${PROJECT_ID}/${REPO_NAME}/django-sandbox:v1"
docker build -f Dockerfile.sandbox -t $SANDBOX_REPO .
docker push $SANDBOX_REPO

4- إعداد التنسيق والتنفيذ

الآن، ننشر مجموعة Ray للتنسيق وموارد وضع الحماية للتنفيذ.

1- إعداد مجموعة Ray

انشر موردًا مخصّصًا من RayCluster. يُرجى العِلم أنّ الموارد المتاحة في مجموعتك (مثل الذاكرة أو وحدة المعالجة المركزية أو نوع وحدة معالجة الرسومات) قد تختلف. اضبط طلبات resources وحدودها وفقًا لذلك.

شغِّل الأمر التالي لإنشاء raycluster.yaml. يستخدم هذا الأمر cat << EOF لاستبدال المتغيرات البيئية تلقائيًا في البيان:

cat << EOF > raycluster.yaml
apiVersion: ray.io/v1
kind: RayCluster
metadata:
  name: grpo-cluster
  namespace: default
spec:
  rayVersion: "2.55.1"
  headGroupSpec:
    rayStartParams:
      dashboard-host: "0.0.0.0"
    template:
      spec:
        containers:
        - name: ray-head
          image: ${REGION}-docker.pkg.dev/${PROJECT_ID}/${REPO_NAME}/ray-head:v1
          ports:
          - containerPort: 6379
            name: gcs-server
          - containerPort: 8265
            name: dashboard
          - containerPort: 10001
            name: client
          resources:
            limits:
              cpu: "2"
              memory: "8Gi"
            requests:
              cpu: "2"
              memory: "8Gi"
  workerGroupSpecs:
  - groupName: gpu-group
    replicas: 1
    minReplicas: 1
    maxReplicas: 1
    rayStartParams: {}
    template:
      spec:
        containers:
        - name: ray-worker
          image: ${REGION}-docker.pkg.dev/${PROJECT_ID}/${REPO_NAME}/ray-gpu-worker:v1
          resources:
            limits:
              cpu: "12"
              memory: "120Gi"
              nvidia.com/gpu: "1"
            requests:
              cpu: "12"
              memory: "120Gi"
              nvidia.com/gpu: "1"
EOF

طبِّقه:

kubectl apply -f raycluster.yaml

تأكَّد من إنشاء المجموعة وتشغيلها (قد يستغرق ذلك بضع دقائق):

kubectl get raycluster

الناتج المتوقّع:

NAME       DESIRED WORKERS   AVAILABLE WORKERS   CPUS   MEMORY   GPUS   STATUS   AGE
rl-cluster   1                 1                                            ready    2m

2- إعداد SandboxRouter

يعمل SandboxRouter كبوابة HTTP عالية السرعة، حيث يتلقّى الطلبات من العاملين في Ray ويربطها على الفور بحاويات gVisor المتاحة، ما يؤدي إلى تجاوز دورة حياة حاوية خادم Kubernetes API الأبطأ.

شغِّل الأمر التالي لإنشاء sandbox_router.yaml:

cat << 'EOF' > sandbox_router.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: default
  name: sandbox-claim-manager
rules:
- apiGroups: ["extensions.agents.x-k8s.io"]
  resources: ["sandboxclaims"]
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ["agents.x-k8s.io"]
  resources: ["sandboxes"]
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: sandbox-claim-manager-binding
  namespace: default
subjects:
- kind: ServiceAccount
  name: default
  namespace: default
roleRef:
  kind: Role
  name: sandbox-claim-manager
  apiGroup: rbac.authorization.k8s.io
---
apiVersion: v1
kind: Service
metadata:
  name: sandbox-router
  namespace: default
spec:
  type: ClusterIP
  selector:
    app: sandbox-router
  ports:
  - name: http
    protocol: TCP
    port: 8080
    targetPort: 8080
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: sandbox-router-deployment
  namespace: default
spec:
  replicas: 2
  selector:
    matchLabels:
      app: sandbox-router
  template:
    metadata:
      labels:
        app: sandbox-router
    spec:
      containers:
      - name: router
        image: us-central1-docker.pkg.dev/k8s-staging-images/agent-sandbox/sandbox-router:latest-main
        ports:
        - containerPort: 8080
        env:
        - name: ALLOW_UNAUTHENTICATED_ROUTER
          value: "true"
EOF

طبِّقه:

kubectl apply -f sandbox_router.yaml

تأكَّد من أنّ عملية النشر قيد التشغيل:

kubectl get deployment sandbox-router-deployment

الناتج المتوقّع:

NAME                        READY   UP-TO-DATE   AVAILABLE   AGE
sandbox-router-deployment   2/2     2            2           1m

3- إعداد SandboxTemplate وWarmPool

تتيح GKE Agent Sandbox إمكانية تخصيص الحاويات المعزولة التي تم تسخينها مسبقًا على الفور باستخدام Sandbox Router. نحدّد SandboxTemplate وSandboxWarmPool لإبقاء الحاويات جاهزة.

شغِّل الأمر التالي لإنشاء sandbox_warmpool.yaml باستخدام المتغيرات البيئية:

cat << EOF > sandbox_warmpool.yaml
apiVersion: extensions.agents.x-k8s.io/v1alpha1
kind: SandboxTemplate
metadata:
  name: swe-bench-django
  namespace: default
spec:
  podTemplate:
    spec:
      runtimeClassName: gvisor
      securityContext:
        runAsNonRoot: true
        runAsUser: 1000
      nodeSelector:
        sandbox.gke.io/runtime: gvisor
      tolerations:
      - key: sandbox.gke.io/runtime
        operator: Equal
        value: gvisor
        effect: NoSchedule
      containers:
      - name: sandbox
        image: ${REGION}-docker.pkg.dev/${PROJECT_ID}/${REPO_NAME}/django-sandbox:v1
        securityContext:
          allowPrivilegeEscalation: false
          capabilities:
            drop:
            - ALL
        resources:
          requests:
            cpu: "2"
            memory: "4Gi"
          limits:
            cpu: "2"
            memory: "4Gi"
---
apiVersion: extensions.agents.x-k8s.io/v1alpha1
kind: SandboxWarmPool
metadata:
  name: swe-bench-django-warmpool
  namespace: default
spec:
  replicas: 10
  sandboxTemplateRef:
    name: swe-bench-django
EOF

طبِّقه:

kubectl apply -f sandbox_warmpool.yaml

تأكَّد من تهيئة SandboxWarmPool:

kubectl get sandboxwarmpool

الناتج المتوقّع:

NAME                        READY   AGE
swe-bench-django-warmpool   10      1m

4- العزل الأمني

تعزل NetworkPolicy عمليات وضع الحماية بشكلٍ صارم، ما يمنع الخروج إلى خادم البيانات الوصفية في GCP، وبالتالي يمنع سرقة رموز إدارة الهوية وإمكانية الوصول.

شغِّل الأمر التالي لإنشاء network_policy.yaml:

cat << 'EOF' > network_policy.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: block-metadata-egress
  namespace: default
spec:
  podSelector:
    matchLabels:
      sandbox.gke.io/runtime: gvisor
  policyTypes:
  - Egress
  egress:
  - to:
    - ipBlock:
        cidr: 0.0.0.0/0
        except:
        - 169.254.169.254/32
EOF

طبِّق السياسة:

kubectl apply -f network_policy.yaml

تأكَّد من إنشاء NetworkPolicy:

kubectl get networkpolicy

الناتج المتوقّع:

NAME                 POD-SELECTOR     AGE
block-metadata-egress             sandbox.gke.io/runtime=gvisor     1m

5- مهمة أساسية للتعلم المعزّز باستخدام SweBench وTRL

بعد إعداد المجموعة وعمليات وضع الحماية، يمكننا تشغيل حلقة تدريب GRPO. سنستخدم مكتبة trl لتنسيق خوارزمية GRPO، ودوال ray عن بُعد لتقييم الرمز الذي تم إنشاؤه داخل عمليات وضع الحماية المعزولة.

لإجراء عملية التنفيذ بسرعة في هذا الدرس التطبيقي حول الترميز، سنفلتر النتائج لعرض مشكلة واحدة في Django. توضّح منطق التوجيه أدناه كيفية اختيار مجموعات وضع الحماية المختلفة للمستودعات المختلفة، وهو أمر مفيد عند التوسّع إلى مجموعة بيانات SWE-bench الكاملة.

نص التدريب البرمجي

شغِّل الأمر التالي لإنشاء train_trl.py:

cat << 'EOF' > train_trl.py
import ray
from k8s_agent_sandbox import SandboxClient
from k8s_agent_sandbox.models import SandboxDirectConnectionConfig
from trl import GRPOConfig, GRPOTrainer
from transformers import AutoModelForCausalLM, AutoTokenizer
from datasets import load_dataset
import urllib.request
import re

ray.init(ignore_reinit_error=True)

# 1. Define the Ray remote evaluation function
@ray.remote
def evaluate_rollout(code, prompt_data):
    client = SandboxClient(connection_config=SandboxDirectConnectionConfig(api_url="http://sandbox-router.default.svc.cluster.local:8080"))
    
    # Claim a pre-warmed sandbox instantly based on the repo
    repo = prompt_data.get("repo")
    
    # In a full system, you'd route to different warmpools based on repo
    # Here we default to django for our single task
    sandbox = client.create_sandbox(
        template="swe-bench-django",
        warmpool="swe-bench-django-warmpool",
        sandbox_ready_timeout=600
    )
    
    try:
        # Check if the code is correctly formatted
        bash_match = re.search(r"```bash\n(.*?)\n```", code, re.DOTALL)
        if not bash_match:
            return 0.0
            
        script = bash_match.group(1)

        # In a real environment, we would apply the base commit and install here
        # For simplicity, we just execute the script
        import shlex
        script_cmd = f"bash -c {shlex.quote(script)}"
        result = sandbox.commands.run(script_cmd, timeout=60)
        
        # Calculate continuous reward based on test passage ratio
        if result.exit_code == 0:
            return 1.0
        
        # Very simple heuristic reward
        return 0.1
        
    finally:
        # Clean up and release the sandbox back to the pool
        client.delete_sandbox(sandbox.claim_name)

# 2. Define the Reward Function for TRL
def sandbox_reward_func(prompts, completions, **kwargs):
    # Dispatch evaluation to Ray cluster
    futures = [
        evaluate_rollout.remote(completion, {
            "repo": kwargs.get('repo', [])[i] if 'repo' in kwargs else None,
            "base_commit": kwargs.get('base_commit', [])[i] if 'base_commit' in kwargs else None
        }) for i, completion in enumerate(completions)
    ]
    
    # Block and wait for all sandbox evaluations to complete
    rewards = ray.get(futures)
    return rewards

# 3. Setup GRPO Trainer
@ray.remote(num_gpus=1, num_cpus=8)
def train():
    # Load dataset
    dataset = load_dataset("princeton-nlp/SWE-bench_Lite", split="test")
    # Filter to our selected target issue
    dataset = dataset.filter(lambda x: x["instance_id"] == "django__django-15388")
    
    def format_dataset(example):
        files = re.findall(r'^\+\+\+ b/(.+)$', example["patch"], re.MULTILINE)
        target_file = files[0] if files else ""
        
        file_content = ""
        if target_file:
            try:
                github_repo = example["repo"]
                url = f"https://raw.githubusercontent.com/{github_repo}/{example['base_commit']}/{target_file}"
                with urllib.request.urlopen(url) as response:
                    file_content = response.read().decode('utf-8')
            except Exception as e:
                pass
                
        prompt = f"""You are an expert software engineer.
You are given a GitHub issue and the content of the file that contains the bug.
Write an executable bash script that will modify the target file to fix the bug (e.g. using cat << 'EOF' > {target_file} or inline python edits).
Wrap your bash script in ```bash ... ``` tags. Do not output raw python code directly.

Target File: {target_file}

Original File Content:
```python
{file_content}
```

Issue:
{example['problem_statement']}
"""
        return {
            "prompt": prompt,
            "repo": example["repo"],
            "instance_id": example["instance_id"],
            "base_commit": example["base_commit"],
        }
        
    dataset = dataset.map(format_dataset)

    model_name = "Qwen/Qwen2.5-Coder-1.5B-Instruct"
    tokenizer = AutoTokenizer.from_pretrained(model_name)

    training_args = GRPOConfig(
        output_dir="outputs",
        learning_rate=5e-6,
        max_steps=50,
        per_device_train_batch_size=1,
        gradient_accumulation_steps=4,
        num_generations=4,
    )

    trainer = GRPOTrainer(
        model=model_name,
        processing_class=tokenizer,
        reward_funcs=[sandbox_reward_func],
        args=training_args,
        train_dataset=dataset,
    )

    print("Starting GRPO training with GKE Agent Sandboxes...")
    trainer.train()

def main():
    print("Submitting training job to GPU worker...")
    ray.get(train.remote())

if __name__ == "__main__":
    main()
EOF

إرسال المهمة إلى المجموعة

أولاً، أعِد توجيه المنفذ إلى لوحة بيانات Ray Head وأرسِل مهمة التدريب من جهازك:

kubectl port-forward service/grpo-cluster-head-svc 8265:8265 &

ray job submit \
  --address http://localhost:8265 \
  --runtime-env-json '{"working_dir": "."}' \
  -- python train_trl.py

مراقبة عملية التشغيل

يمكنك مراقبة مستوى تقدُّم عملية التشغيل:

  • لوحة بيانات Ray: افتح http://localhost:8265 في متصفّحك.
  • مطالبات وضع الحماية: راقِب كيف يطالب GKE بعمليات وضع الحماية ويحرّرها بشكلٍ ديناميكي ضمن gVisor:
    watch -n 1 "kubectl get sandboxclaims,sandboxes,pods"
    

6- الخاتمة

تهانينا! لقد نجحت في إعداد حلقة تدريب موزّعة عالية الأداء للتعلم المعزّز وتنفيذها بأمان على GKE Standard باستخدام GKE Agent Sandboxes.