すべてのプロダクト
Search
ドキュメントセンター

Container Service for Kubernetes:ACK での強化学習ジョブの実行

最終更新日:Apr 25, 2026

このドキュメントでは、VeRL フレームワークおよび Qwen2.5-3B-Instruct モデルを使用して、ACK クラスター上で典型的な強化学習ジョブを実行する方法について説明します。環境準備、イメージのビルド、ジョブの送信、リソースのモニタリング、ベストプラクティスが含まれます。

Container Service for Kubernetes (ACK) は、企業向けに効率的で弾力性があり、スケーラブルなコンテナ化プラットフォームを提供します。強化学習(RL)は人工知能の主要な分野の一つであり、多くの場合、大量のコンピューティングリソース、分散トレーニング、複雑な環境シミュレーションを必要とします。ACK を使用することで、Kubernetes のスケジューリング機能と Alibaba Cloud の弾力的なインフラストラクチャを利用して、強化学習のトレーニングジョブを簡単にデプロイ、管理、スケーリングできます。次の図は、このジョブのコンポーネントアーキテクチャを示しています。

image

前提条件

  1. ACK マネージドクラスターを作成済みです。

    • トレーニングを高速化するために GPU インスタンスの使用を推奨します。この例では、8 個の GU8TF GPU を搭載した Lingjun ノード 1 台を使用します。

  2. クラスターの kubeconfig を取得済みで、kubectl を使用してクラスターに接続済みです。

  3. KubeRay Operator コンポーネントをインストール済みです。

  4. (オプション)Object Storage Service (OSS) を有効にして、モデルチェックポイント、ログ、トレーニングデータを永続化します。

ステップ 1:トレーニングイメージの準備

この例では、強化学習ジョブを実行するために VeRL フレームワークを使用します。公式の VeRL イメージを使用するか、独自にビルドできます。独自のイメージをビルドする場合は、VeRL、vLLM、SGLang、Ray などの必要なすべての依存関係が含まれていることを確認してください。以下は Dockerfile の例です:

from verl/verl:vllm012.latest

WORKDIR /home/verl

COPY . .

RUN apt update && apt install -y openssh-server vim
RUN apt remove python3-blinker -y; pip install -e .

ステップ 2:MCP Server および ACK Sandbox の構成

  1. MCP Server および Sandbox コンポーネントをインストールします。

    オープンソース版

    # コードリポジトリをクローン
    git clone https://github.com/openkruise/agents
    cd agents
    # エージェントオペレーターのデプロイ YAML を生成
    kubectl kustomize config/default >operator-install.yaml
    # 必要に応じて operator-install.yaml の構成を変更
    kubectl apply -f operator-install.yaml
    
    # テスト用 sandbox-manager をデプロイ。詳細については、https://github.com/openkruise/agents/blob/master/config/sandbox-manager/README.md を参照
    kubectl kustomize config/sandbox-manager >sandbox-manager.yaml
    # MCP コードはまだマージされていないため、sandbox-manager イメージを手動で次のように変更する必要があります:
    # baicun-business-registry.cn-beijing.cr.aliyuncs.com/baicun-dev/sandbox:sandbox-manager-v12
    
    # 管理用 Pod が正しく実行されていることを確認
    kubectl get pod -l "app.kubernetes.io/name=sandbox-manager" -A
    kubectl get pod -l "app.kubernetes.io/name=sandbox-controller-manager" -A

    マーケットプレイス バージョン

    1. クラスターリストページで、対象のクラスター名をクリックします。左側のナビゲーションウィンドウで、Add-onsを選択します。

    2. Ingress Controller および Sandbox 関連コンポーネントをインストールします。

      1. インストール ack-agent-sandbox-controller

        デフォルト構成でコンポーネントをインストールします。

      2. インストール ack-sandbox-manager

        1. E2B ドメイン名を準備します。

          ドメイン名の準備、DNS 解決の構成、証明書の申請に関する詳細な手順については、「本番環境での使用」をご参照ください。

        2. コンポーネントパラメーターを構成します。

          classNamealb(この例では インストール済みの ALB Ingress Controller を使用)、domain は実際のドメイン名、adminApiKey はカスタム API キーに設定します。その他の設定はデフォルト値のままにしてください。インストール後、sandbox-system 名前空間に sandbox-manager という名前の Ingress が作成されます。

          パラメーターの詳細

          パラメーター

          パラメーター

          説明

          sandboxManager

          replicaCount

          sandbox-manager インスタンスの数。デフォルト:3。

          E2B

          domain

          前述の手順で準備した E2B ドメイン名。

          Enable E2B_API_KEY verification

          API_KEY 認証を有効にするかどうかを指定します。デフォルトで有効です。

          adminApiKey

          認証が有効な場合、初回インストール時に初期キーを指定するためにこのパラメーターを使用します。カスタム API キーに設定してください。

          Controller

          logLevel

          コントローラーのログレベル。デフォルト:1。

          resources.requests.cpu

          コントローラーが要求する CPU リソース。デフォルト:2。

          resources.requests.memory

          コントローラーが要求するメモリリソース。デフォルト:4Gi。

          Proxy

          resources.requests.cpu

          プロキシが要求する CPU リソース。デフォルト:2。

          resources.requests.memory

          プロキシが要求するメモリリソース。デフォルト:4Gi。

          Ingress

          className

          クラスター内で構成された IngressClass の名前(例:alb または mse)。

        3. ALB Ingress Controller を使用している場合は、ALB インスタンスおよび Ingress の両方に HTTPS:443 リスナー構成を追加する必要があります。

          AlbConfig を更新して、ALB インスタンスに HTTPS:443 リスナーを追加します。

          1. 左側のナビゲーションウィンドウで、ワークロード > カスタムリソースを選択します。リソースオブジェクト タブで AlbConfig を検索し、検索結果をクリックします。

          2. AlbConfig リソースオブジェクトの一覧で、alb リソースを見つけ、YAML の編集アクション列でクリックします。

          3. spec.listeners.port: 443 および spec.listeners.protocol: HTTPS フィールドを追加し、OK をクリックします。

            image

          Ingress を更新して HTTPS:443 リスナーを関連付けます。

          1. 左側のナビゲーションウィンドウで、ネットワーク > Ingressを選択します。sandbox-manager Ingress について、更新アクション列でクリックします。

          2. 以下の構成を追加し、OK をクリックします。

            • 注釈: alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS": 443}]'

  2. 以下の内容を sandbox.yaml として保存し、kubectl apply -f sandbox.yaml を実行して Sandbox 定義をデプロイします。SandboxSet はサイズ 3 のウォームプールを作成します。強化学習中、SandboxManager はこのウォームプールから継続的に Sandbox を消費します。

    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: mcp-sandbox
    spec:
      selector:
        app.kubernetes.io/instance: release-name
        app.kubernetes.io/name: ack-sandbox-manager
        component: sandbox-manager
      type: ClusterIP
      sessionAffinity: None
      sessionAffinityConfig:
        clientIP:
          timeoutSeconds: 10800
      ports:
      - name: vllm
        protocol: TCP
        port: 8000
        targetPort: 18082
    ---
    apiVersion: agents.kruise.io/v1alpha1
    kind: SandboxSet
    metadata:
      annotations:
        # SandboxManager の Envd 初期化機能を有効化。
        e2b.agents.kruise.io/should-init-envd: "true"
      name: code-interpreter
      namespace: default
    spec:
      # ウォームプールのサイズ。リクエストバーストの見積もりよりやや大きく設定することを推奨します。
      replicas: 3
      template:
        spec:
          initContainers:
            - name: init
              image: registry-cn-hangzhou.ack.aliyuncs.com/acs/agent-runtime:v0.0.1
              imagePullPolicy: IfNotPresent
              terminationMessagePolicy: File
              volumeMounts:
                - name: envd-volume
                  mountPath: /mnt/envd
              env:
                - name: ENVD_DIR
                  value: /mnt/envd
              restartPolicy: Always
          containers:
            - name: sandbox
              image: acs-image-test-01-registry.cn-hangzhou.cr.aliyuncs.com/e2b/code-interpreter:v1.6
              imagePullPolicy: IfNotPresent
              terminationMessagePolicy: File
              env:
                - name: ENVD_DIR
                  value: /mnt/envd
              volumeMounts:
                - name: envd-volume
                  mountPath: /mnt/envd
              lifecycle:
                postStart:
                  exec:
                    command:
                      - bash
                      - /mnt/envd/envd-run.sh
              startupProbe:
                failureThreshold: 20
                successThreshold: 1
                httpGet:
                  path: /health
                  port: 49999
                  scheme: HTTP
                initialDelaySeconds: 1
                periodSeconds: 2
                timeoutSeconds: 1
          # 再利用の確率を高めるために、コンテナの終了を迅速に行います。
          terminationGracePeriodSeconds: 1
          restartPolicy: Always
          dnsPolicy: ClusterFirst
          volumes:
            - name: envd-volume
              emptyDir: { }

(オプション)ステップ 3:データセットの準備

VeRL では、data.train_files を指定することで、リモートソースからデータセットをダウンロードできます。ただし、データセットは通常大規模で前処理が必要なため、本番環境では前処理ジョブを使用してデータをダウンロード・前処理し、クラウドストレージにアップロードすることを推奨します。

  1. 以下の内容を data.yaml として保存し、kubectl apply -f data.yaml を実行して Hugging Face からデータをダウンロード・前処理し、OSS バケットにアップロードします。

    apiVersion: v1
    kind: Secret
    metadata:
      name: hf-oss-credentials
      namespace: default
    type: Opaque
    stringData:
      # Hugging Face トークン
      HF_TOKEN: "hf_xxxxx"
      # Alibaba Cloud OSS 認証情報(alibabacloud-oss-v2 SDK は環境変数による認証を使用)
      akId: "xxx"
      akSecret: "xxx"
      OSS_REGION: "xxx"
      OSS_BUCKET: "xxx"
    ---
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: preprocess-script
      namespace: default
    data:
      preprocess.py: |
        #!/usr/bin/env python3
        """
        データセット前処理スクリプトの例
        """
        import os
        import json
        from datasets import load_from_disk
        
        def preprocess_dataset(input_dir, output_dir):
            """データセットを前処理"""
            print(f"データセットを {input_dir} から読み込み中")
            dataset = load_from_disk(input_dir)
            
            train_dataset = dataset["train"]
            test_dataset = dataset["test"]
    
            instruction_following = "Let's think step by step and output the final answer after `####`."
        
            # 各データ項目に一意の ID を表す行を追加
            def make_map_fn(split):
                def process_fn(example, idx):
                    question_raw = example.pop("question")
        
                    question = question_raw + " " + instruction_following
        
                    answer_raw = example.pop("answer")
                    solution = extract_solution(answer_raw)
                    data = {
                        "data_source": data_source,
                        "agent_name": "tool_agent",
                        "prompt": [
                            {
                                "role": "system",
                                "content": (
                                    "You are a math expert. You are given a question and you need to solve it step by step. "
                                    "Reasoning step by step before any tool call. "
                                    "You should use the `calc_gsm8k_reward` tool after step by step solving the question, "
                                    "before generate final answer at least once and refine your answer if necessary. "
                                    "Put your final answer in the format of `#### <answer>`."
                                ),
                            },
                            {
                                "role": "user",
                                "content": question,
                            },
                        ],
                        "ability": "math",
                        "reward_model": {"style": "rule", "ground_truth": solution},
                        "extra_info": {
                            "split": split,
                            "index": idx,
                            "answer": answer_raw,
                            "question": question_raw,
                            "need_tools_kwargs": True,
                            "tools_kwargs": {
                                "calc_gsm8k_reward": {
                                    "create_kwargs": {"ground_truth": solution},
                                    # "execute_kwargs": {},
                                    # "calc_reward_kwargs": {},
                                    # "release_kwargs": {},
                                },
                            },
                            "interaction_kwargs": {
                                "query": question,
                                "ground_truth": solution,
                            },
                        },
                    }
                    return data
        
                return process_fn
    
            train_dataset = train_dataset.map(function=make_map_fn("train"), with_indices=True, num_proc=8)
            test_dataset = test_dataset.map(function=make_map_fn("test"), with_indices=True, num_proc=8)
            
            # 処理済みデータセットを保存
            os.makedirs(output_dir, exist_ok=True)
            train_dataset.to_parquet(os.path.join(output_dir, "train.parquet"))
            test_dataset.to_parquet(os.path.join(output_dir, "test.parquet"))
            print(f"処理済みデータセットを {output_dir} に保存しました")
            
            return output_dir
        
        if __name__ == "__main__":
            input_path = os.environ.get("INPUT_PATH", "/data/raw")
            output_path = os.environ.get("OUTPUT_PATH", "/data/processed")
            preprocess_dataset(input_path, output_path)
    ---
    apiVersion: batch/v1
    kind: Job
    metadata:
      name: dataset-pipeline
      namespace: default
      labels:
        app: dataset-pipeline
    spec:
      backoffLimit: 3
      template:
        metadata:
          labels:
            app: dataset-pipeline
        spec:
          restartPolicy: OnFailure
          volumes:
            # 前処理スクリプト
            - name: scripts
              configMap:
                name: preprocess-script
                defaultMode: 0755
          containers:
            - name: dataset-pipeline
              image: python:3.10-slim
              command:
                - /bin/bash
                - -c
                - |
                  set -e
                  
                  #==========================================
                  # ステップ 1:すべての依存関係をインストール
                  #==========================================
                  echo "=== 依存関係をインストール中 ==="
                  pip install --no-cache-dir datasets huggingface_hub pandas numpy alibabacloud-oss-v2 Pillow
                  
                  #==========================================
                  # ステップ 2:Hugging Face からデータセットをダウンロード
                  #==========================================
                  echo "=== Hugging Face からデータセットをダウンロード中 ==="
                  python3 << 'EOF'
                  import os
                  from datasets import load_dataset
                  from huggingface_hub import login
                  
                  # Hugging Face にログイン(非公開データセットに必要)
                  hf_token = os.environ.get("HF_TOKEN")
                  if hf_token:
                      login(token=hf_token)
                  
                  # データセットをダウンロード
                  dataset_name = os.environ.get("DATASET_NAME", "hiyouga/geometry3k")
                  dataset_config = os.environ.get("DATASET_CONFIG", None)
                  
                  print(f"データセットをダウンロード中: {dataset_name}")
                  dataset = load_dataset(dataset_name, dataset_config)
                  
                  # ローカルに保存
                  output_path = "/data/raw"
                  dataset.save_to_disk(output_path)
                  print(f"データセットを {output_path} に保存しました")
                  EOF
                  echo "=== ダウンロード完了 ==="
                  
                  #==========================================
                  # ステップ 3:前処理スクリプトを実行
                  #==========================================
                  echo "=== 前処理スクリプトを実行中 ==="
                  python3 /scripts/preprocess.py
                  echo "=== 前処理完了 ==="
                  
                  #==========================================
                  # ステップ 4:OSS にアップロード(alibabacloud-oss-v2 SDK を使用)
                  #==========================================
                  echo "=== OSS にアップロード中 ==="
                  python3 << 'EOF'
                  import os
                  from pathlib import Path
                  import alibabacloud_oss_v2 as oss
                  
                  # OSS 構成
                  bucket_name = os.environ["OSS_BUCKET"]
                  region = os.environ["OSS_REGION"]
                  oss_prefix = os.environ.get("OSS_PREFIX", "data/geo3k-processed/")
                  local_path = os.environ.get("OUTPUT_PATH", "/data/processed")
                  
                  # 環境変数認証情報プロバイダーを使用(OSS_ACCESS_KEY_ID および OSS_ACCESS_KEY_SECRET を自動的に読み取り)
                  credentials_provider = oss.credentials.EnvironmentVariableCredentialsProvider()
                  
                  # デフォルト構成を読み込み、認証情報プロバイダーを設定
                  cfg = oss.config.load_default()
                  cfg.credentials_provider = credentials_provider
                  cfg.region = region
                  
                  # OSS クライアントを作成
                  client = oss.Client(cfg)
                  
                  def upload_directory(local_dir, oss_prefix):
                      """ディレクトリを再帰的に OSS にアップロード"""
                      local_path = Path(local_dir)
                      uploaded_count = 0
                      failed_count = 0
                      
                      for file_path in local_path.rglob("*"):
                          if file_path.is_file():
                              relative_path = file_path.relative_to(local_path)
                              oss_key = f"{oss_prefix}{relative_path}"
                              
                              try:
                                  # ファイル内容を読み取り
                                  with open(file_path, 'rb') as f:
                                      data = f.read()
                                  
                                  # OSS にアップロード
                                  result = client.put_object(oss.PutObjectRequest(
                                      bucket=bucket_name,
                                      key=oss_key,
                                      body=data,
                                  ))
                                  print(f"アップロード完了: {file_path} -> {oss_key} (ステータス: {result.status_code})")
                                  uploaded_count += 1
                              except Exception as e:
                                  print(f"{file_path} のアップロードに失敗: {e}")
                                  failed_count += 1
                      
                      return uploaded_count, failed_count
                  
                  uploaded, failed = upload_directory(local_path, oss_prefix)
                  print(f"=== アップロード完了: {uploaded} 件のファイルをアップロード、{failed} 件のファイルが失敗 ===")
                  if failed > 0:
                      raise Exception(f"{failed} 件のファイルのアップロードに失敗しました")
                  EOF
                  
                  echo "=== パイプラインが正常に完了しました ==="
              env:
                # Hugging Face 構成
                - name: HF_TOKEN
                  valueFrom:
                    secretKeyRef:
                      name: hf-oss-credentials
                      key: HF_TOKEN
                - name: DATASET_NAME
                  value: "hiyouga/geometry3k"
                - name: HF_HOME
                  value: "/tmp/huggingface"
                # 前処理構成
                - name: INPUT_PATH
                  value: "/data/raw"
                - name: OUTPUT_PATH
                  value: "/data/processed"
                # OSS 構成
                - name: OSS_ACCESS_KEY_ID
                  valueFrom:
                    secretKeyRef:
                      name: hf-oss-credentials
                      key: akId
                - name: OSS_ACCESS_KEY_SECRET
                  valueFrom:
                    secretKeyRef:
                      name: hf-oss-credentials
                      key: akSecret
                - name: OSS_REGION
                  valueFrom:
                    secretKeyRef:
                      name: hf-oss-credentials
                      key: OSS_REGION
                - name: OSS_BUCKET
                  valueFrom:
                    secretKeyRef:
                      name: hf-oss-credentials
                      key: OSS_BUCKET
                - name: OSS_PREFIX
                  value: "data/geo3k-processed/"
              volumeMounts:
                - name: scripts
                  mountPath: /scripts
              resources:
                requests:
                  memory: "2Gi"
                  cpu: "1"
                limits:
                  memory: "16Gi"
                  cpu: "4"
    

ステップ 4:ジョブ構成の適用

  1. 以下の内容を pvpvc.yaml として保存し、kubectl apply -f pvpvc.yaml を実行して、PersistentVolume および PersistentVolumeClaim を使用して OSS 静的 PersistentVolume をマウントします。

    以下の例では、AccessKey ペアを使用した認証を行っています。RRSA 認証を使用する場合は、「ossfs 2.0 を使用した静的 PersistentVolume の利用」をご参照ください。
    apiVersion: v1
    kind: PersistentVolume
    metadata:
      name: ym-dataset
      labels:
        alicloud-pvname: ym-dataset
    spec:
      capacity:
        storage: 20Gi
      accessModes:
        - ReadWriteMany
      persistentVolumeReclaimPolicy: Retain
      csi:
        driver: ossplugin.csi.alibabacloud.com
        volumeHandle: ym-dataset # PV 名と同じである必要があります。
        nodePublishSecretRef:
          name: hf-oss-credentials
          namespace: default
        volumeAttributes:
          bucket: "xxxx" # 実際のバケット名に置き換えてください。
          url: "oss-ap-southeast-1-internal.aliyuncs.com" # 実際の OSS エンドポイントに置き換えてください。
          otherOpts: "-o umask=022 -o max_stat_cache_size=100000 -o allow_other -o dbglevel=debug -o curldbg"
          path: "/"
    ---
    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
      name: ym-dataset
    spec:
      accessModes:
        - ReadWriteMany
      resources:
        requests:
          storage: 20Gi
      selector:
        matchLabels:
          alicloud-pvname: ym-dataset
          
    # (オプション)Hugging Face リポジトリパスを指定することで、モデルをオンデマンドでダウンロードできます。
    ---
    apiVersion: v1
    kind: PersistentVolume
    metadata:
      name: ym-models
      labels:
        alicloud-pvname: ym-models
    spec:
      capacity:
        storage: 20Gi
      accessModes:
        - ReadWriteMany
      persistentVolumeReclaimPolicy: Retain
      csi:
        driver: ossplugin.csi.alibabacloud.com
        volumeHandle: ym-models # PV 名と同じである必要があります。
        nodePublishSecretRef:
          name: hf-oss-credentials
          namespace: default
        volumeAttributes:
          bucket: "xxxx" # 実際のバケット名に置き換えてください。
          url: "oss-ap-southeast-1-internal.aliyuncs.com" # 実際の OSS エンドポイントに置き換えてください。
          otherOpts: "-o umask=022 -o max_stat_cache_size=100000 -o allow_other -o dbglevel=debug -o curldbg"
          path: "/"
    ---
    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
      name: ym-models
    spec:
      accessModes:
        - ReadWriteMany
      resources:
        requests:
          storage: 20Gi
      selector:
        matchLabels:
          alicloud-pvname: ym-models
  2. 以下の内容を configs.yaml として保存し、kubectl apply -f configs.yaml を実行して、ジョブ関連の構成を適用します。

    ---
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: gsm8k-configs
      namespace: default
    data:
      gsm8k_multiturn_grpo.yaml: |
        hydra:
          searchpath:
            - file://verl/trainer/config
        defaults:
          - ppo_trainer
          - _self_
        data:
          max_prompt_length: 1024
          max_response_length: 1024
          train_batch_size: 256
          return_raw_chat: True
        actor_rollout_ref:
          hybrid_engine: True
          rollout:
            name: vllm
            multi_turn:
              enable: True
              max_assistant_turns: 5
      mcp_server.json: |
        {
            "mcpServers": {
                "Tavily Expert": {
                    "url": "xxxxx", # Sandbox MCP Ingress エンドポイントに置き換えてください。
                    "api_key": "xxxxx" # API キーが必要な場合は、Ray クラスターに NGINX コンテナをプロキシとして追加できます。
                }
            }
        }
      gsm8k_mcp_tool_config.yaml: |
        tools:
        - class_name: verl.tools.mcp_search_tool.MCPSearchTool
          config:
            rate_limit: 120
            timeout: 120
            type: mcp
          mcp:
            mcp_servers_config_path: /var/configs/mcp_server.json
            tool_selected_list: 
              - run_code_once
        - class_name: "verl.tools.gsm8k_tool.Gsm8kTool"
          config: 
            type: native
          tool_schema:
            type: "function"
            function:
              name: "calc_gsm8k_reward"
              description: "GSM8K の報酬を計算するツール。(解析された回答が正しい場合は 1.0、解析された回答が間違っているか正しく解析されていない場合は 0.0)"
              parameters:
                type: "object"
                properties:
                  answer:
                    type: "string"
                    description: "GSM8K 数学問題に対するモデルの回答。数字である必要があります。"
                required: ["answer"]

ステップ 5:ジョブの送信

VeRL では、MCPSearchTool を使用して MCP Server が提供するツールをクエリできます。各ユースケースの開始時に、AgentLoop が MCP Server に接続し、マルチターン対話中にツールを呼び出します。

  1. 以下の内容を rayjob.yaml として保存し、kubectl apply -f rayjob.yaml を実行して強化学習ジョブを送信します。

    ---
    apiVersion: ray.io/v1
    kind: RayJob
    metadata:
      name: rayjob-example
      namespace: default
    spec:
      shutdownAfterJobFinishes: false
      # ttlSecondsAfterFinished: 300
      runtimeEnvYAML: |
        working_dir: /home/verl
      submissionMode: SidecarMode
      entrypoint: |
        python3 -m verl.trainer.main_ppo \
          --config-path=/var/configs \
          --config-name='gsm8k_multiturn_grpo' \
          algorithm.adv_estimator=grpo \
          data.train_batch_size=16 \
          data.max_prompt_length=1024 \
          data.max_response_length=1024 \
          data.filter_overlong_prompts=True \
          data.truncation='error' \
          data.return_raw_chat=True \
          actor_rollout_ref.model.path=/var/model/Qwen2.5-3B-Instruct \
          actor_rollout_ref.actor.optim.lr=1e-6 \
          actor_rollout_ref.model.use_remove_padding=True \
          actor_rollout_ref.actor.ppo_mini_batch_size=8 \
          actor_rollout_ref.actor.ppo_micro_batch_size_per_gpu=1 \
          actor_rollout_ref.actor.use_kl_loss=True \
          actor_rollout_ref.actor.kl_loss_coef=0.001 \
          actor_rollout_ref.actor.kl_loss_type=low_var_kl \
          actor_rollout_ref.actor.entropy_coeff=0 \
          actor_rollout_ref.model.enable_gradient_checkpointing=True \
          actor_rollout_ref.actor.fsdp_config.param_offload=False \
          actor_rollout_ref.actor.fsdp_config.optimizer_offload=False \
          actor_rollout_ref.rollout.log_prob_micro_batch_size_per_gpu=1 \
          actor_rollout_ref.rollout.tensor_model_parallel_size=2 \
          actor_rollout_ref.rollout.name=vllm \
          actor_rollout_ref.rollout.mode=async \
          actor_rollout_ref.rollout.gpu_memory_utilization=0.5 \
          actor_rollout_ref.rollout.n=16 \
          actor_rollout_ref.ref.log_prob_micro_batch_size_per_gpu=1 \
          actor_rollout_ref.ref.fsdp_config.param_offload=True \
          actor_rollout_ref.rollout.trace.backend=mlflow \
          actor_rollout_ref.rollout.trace.token2text=True \
          algorithm.use_kl_in_reward=False \
          trainer.critic_warmup=0 \
          trainer.logger='["console","mlflow"]' \
          trainer.project_name='gsm8k_tool-agent' \
          trainer.experiment_name='qwen2.5-3b_function_rm-gsm8k-vllm-tool-agent-verify-n16' \
          trainer.n_gpus_per_node=8 \
          trainer.nnodes=1 \
          trainer.save_freq=1 \
          trainer.test_freq=20 \
          trainer.total_training_steps=1 \
          data.train_files=/var/model-dataset/processed-gsm8k/train20.parquet \
          data.val_files=/var/model-dataset/processed-gsm8k/test100.parquet \
          actor_rollout_ref.rollout.multi_turn.tool_config_path="/var/configs/gsm8k_mcp_tool_config.yaml" \
          actor_rollout_ref.actor.checkpoint.save_contents='["hf_model", "model"]' \
          trainer.total_epochs=1 
      rayClusterSpec:
        headGroupSpec:
          rayStartParams:
            dashboard-host: 0.0.0.0
          serviceType: ClusterIP
          template:
            metadata:
              annotations: 
              labels:
            spec:
              affinity: {}
              tolerations:
              - key: node-role.alibabacloud.com/lingjun
              containers:
              - env:
                - name: VERL_ROOT
                  value: /home/verl
                image: registry-ap-southeast-1.ack.aliyuncs.com/dev/verl:vllm012.latest.43dc9a44
                imagePullPolicy: IfNotPresent
                name: ray-head
                resources:
                  limits:
                    cpu: "100"
                    memory: 500Gi
                    nvidia.com/gpu: "8"
                securityContext:
                  runAsUser: 0
                volumeMounts:
                - mountPath: /var/configs
                  name: configs
                - mountPath: /var/model
                  name: model
                - mountPath: /var/model-dataset
                  name: model-dataset
              imagePullSecrets: 
              - name: regcred-hangzhou
              - name: regcred-ap-southeast
              volumes:
              - name: configs
                configMap:
                  name: gsm8k-configs
              - name: model
                persistentVolumeClaim:
                  claimName: ym-models
              - name: model-dataset
                persistentVolumeClaim:
                  claimName: ym-dataset