[train] feat: Add OpenClaw online RL/OPD/Combine training path#76
[train] feat: Add OpenClaw online RL/OPD/Combine training path#76jamindy wants to merge 2 commits into
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces an automated evaluation suite and training pipelines for OpenClaw-RL methods, including Binary RL, On-Policy Distillation (OPD), and a Combine/Top-K-Select hybrid. It adds evaluation scripts, vLLM server launchers, training scripts, custom loss functions, and a gateway session manager. Key feedback from the review highlights critical issues, including potential runtime crashes (ReadonlyContainerError) due to direct modifications of read-only Hydra configurations in entry.py and rollouter.py, a potential memory leak in the RL server from unclosed sessions, and an inaccurate overlap metric in topk_select.py caused by including padded positions in the mean calculation.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| start_time = time() | ||
| auto_set_device(config) | ||
| # Align rollout pool config with actor_rollout_ref rollouter config. | ||
| config.actor_rollout_ref.rollout.nnodes = config.rollout.nnodes | ||
| config.actor_rollout_ref.rollout.n_gpus_per_node = config.rollout.n_gpus_per_node |
There was a problem hiding this comment.
Hydra-instantiated configurations are read-only by default. Modifying config directly (e.g., config.actor_rollout_ref.rollout.nnodes = ...) will raise a ReadonlyContainerError at runtime. To prevent this crash, use OmegaConf.set_readonly(config, False) before making any modifications.
from omegaconf import OmegaConf\n OmegaConf.set_readonly(config, False)\n start_time = time()\n auto_set_device(config)\n # Align rollout pool config with actor_rollout_ref rollouter config.\n config.actor_rollout_ref.rollout.nnodes = config.rollout.nnodes\n config.actor_rollout_ref.rollout.n_gpus_per_node = config.rollout.n_gpus_per_node| try: | ||
| if self.config.get("distillation") is not None and self.config.distillation.get("enabled"): | ||
| self.config.distillation.enabled = False | ||
| logger.info( | ||
| "[OpenClawOPDRollouter] native distillation teacher infra disabled on " | ||
| "rollouter (teacher log-probs computed externally via GenRM)" | ||
| ) | ||
| except Exception as e: # pragma: no cover - defensive | ||
| logger.warning("[OpenClawOPDRollouter] could not disable rollouter distillation: %s", e) |
There was a problem hiding this comment.
If self.config is a read-only DictConfig (which is standard for Hydra-instantiated applications), attempting to modify self.config.distillation.enabled will raise a ReadonlyContainerError. While the try-except block catches this exception and logs a warning, it leaves enabled as True, which subsequently causes the worker initialization to crash on a None teacher client. To ensure this modification succeeds, use OmegaConf.set_readonly(self.config, False) first.
try:\n if self.config.get(\"distillation\") is not None and self.config.distillation.get(\"enabled\"):\n from omegaconf import OmegaConf\n OmegaConf.set_readonly(self.config, False)\n self.config.distillation.enabled = False\n logger.info(\n \"[OpenClawOPDRollouter] native distillation teacher infra disabled on \"\n \"rollouter (teacher log-probs computed externally via GenRM)\"\n )\n except Exception as e: # pragma: no cover - defensive\n logger.warning(\"[OpenClawOPDRollouter] could not disable rollouter distillation: %s\", e)| self._sessions: dict[str, _SessionState] = {} | ||
| self._gateway_sessions: dict[str, SessionHandle] = {} | ||
| self._sessions_lock = asyncio.Lock() |
There was a problem hiding this comment.
The server stores active sessions in self._sessions and self._gateway_sessions indefinitely. If a client disconnects or fails to send a session_done signal, these sessions will never be cleaned up, leading to a memory/resource leak over time. Consider implementing a session timeout or a periodic background cleanup task to evict stale sessions.
| else: # token_optimal | ||
| sel = overlap.argmax(dim=-1) # (B,S) | ||
| overlap_metric = overlap.gather(-1, sel.unsqueeze(-1)).squeeze(-1).mean() |
There was a problem hiding this comment.
The overlap_metric is computed as a simple mean over the entire tensor, which includes padded positions in the batch. Since padded positions will have matching padding tokens (e.g., 0 == 0), the metric distillation/topk_select_overlap will be artificially inflated. To get an accurate metric, mask the gathered overlap with response_mask before computing the mean.
| else: # token_optimal | |
| sel = overlap.argmax(dim=-1) # (B,S) | |
| overlap_metric = overlap.gather(-1, sel.unsqueeze(-1)).squeeze(-1).mean() | |
| else: # token_optimal\n sel = overlap.argmax(dim=-1) # (B,S)\n gathered = overlap.gather(-1, sel.unsqueeze(-1)).squeeze(-1)\n response_mask = data.get(\"response_mask\", None)\n if response_mask is not None:\n mask_bool = response_mask.bool().to_padded_tensor(False) if response_mask.is_nested else response_mask.bool()\n overlap_metric = gathered[mask_bool].mean() if mask_bool.any() else torch.tensor(0.0)\n else:\n overlap_metric = gathered.mean() |
What Does This PR Do?
This PR adds the OpenClaw-RL online training path to
uni-agenton top ofverlfully async training, with support for client-driven RL / OPD / Combine rollout flows.Training data no longer comes from static datasets. Instead, an external OpenClaw client connects to an embedded OpenAI-compatible proxy, drives multi-turn sessions, and submits per-turn training samples directly into the fully async rollout queue.
Specifically:
uni_agent.gatewaywith per-turn export support for OpenClaw traffic.GatewaySessioncan captureprompt_ids,response_ids, and backendresponse_logprobsafter successful generation, reusing the gatewayMessageCodecand avoiding local decode/re-encode.chat_completionsandpop_turn_capturesthroughGatewayActor/GatewayManager, allowing OpenClaw rollouters to drive generation through the existing gateway session runtime.uni_agent.openclaw.rl, including a fully async rollouter, gateway-backed proxy server, sample builder, trainer, and entrypoint for client-driven binary RL.uni_agent.openclaw.opd, including hint judging, teacher-logprob channel, OPD sample construction, distillation trainer wiring, and gateway-only proxy behavior.uni_agent.openclaw.combine, including hybrid RL + OPD sample dispatch, combine loss integration, and mode-specific rollouter/server/trainer wiring.uni_agent.openclaw.losses.The default OpenClaw online training path is now:
OpenClaw client → embedded FastAPI proxy →
GatewayManager/GatewaySession→ rollout vLLM →TurnCapture→ PRM / teacher scoring → per-turnRolloutSample→ fully async trainer.PR Scope
This PR focuses on the OpenClaw online fully async training integration:
uni_agent.gatewayrequired for per-turn capture.uni_agent.openclaw.rluni_agent.openclaw.opduni_agent.openclaw.combineTests
Important regression coverage includes:
openclaw_base,openclaw_rl,openclaw_opd, andopenclaw_combine.API and Usage
Example launch scripts:
End-to-End Evaluation for OpenClaw-RL Training Methods:
Design and Code Changes
High-level structure:
GatewaySessionowns session state andTurnCaptureemission.GatewayActorprovides OpenAI-compatible chat completion RPC and capture draining.GatewayManagerroutes session IDs to gateway actors and exposes the RPCs needed by OpenClaw.OpenClawRLServeris the embedded client-facing proxy.OpenClawRLRollouterowns theGatewayManagerlifecycle and pushes per-turn samples to the fully async message queue.OpenClawOPDRollouterandOpenClawCombineRollouterreuse the RL gateway path and customize scoring/sample construction.openclaw_base.yamlcarries shared fully async config; mode-specific configs only keep deltas.Key invariants:
worker_process_setup_hook.Checklist Before Submitting
pre-commit install && pre-commit run --all-files --show-diff-on-failure --color=always