Repository navigation
[refactor] Rename arctic_rl config namespace to generic remote_backend.arctic #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
eec25e8
b9d2914
d1ff467
188338b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,3 +1,4 @@ | ||||||
| import asyncio | ||||||
| import os | ||||||
| from typing import Any | ||||||
|
|
||||||
|
|
@@ -94,10 +95,15 @@ class ArcticRLClientWrapper(RemoteBackend): | |||||
| it via ``config.trainer.remote_backend = "arctic"``. | ||||||
| """ | ||||||
|
|
||||||
| # Key under `main_config.remote_backend.<name>` where this adapter's | ||||||
| # config lives. Matches the registered backend name above. | ||||||
| _BACKEND_CONFIG_KEY = "arctic" | ||||||
|
|
||||||
| def __init__(self, config, reconnect_job_config: dict = None, rl_server_state: ArcticRLRayServerState = None): | ||||||
| self.config = config | ||||||
| self._backend_config = config.remote_backend[self._BACKEND_CONFIG_KEY] | ||||||
| self._client = None | ||||||
| self.use_zorro = self.config.arctic_rl.use_zorro | ||||||
| self.use_zorro = self._backend_config.use_zorro | ||||||
| self.use_liger = self.config.actor_rollout_ref.model.use_liger | ||||||
| # Static, config-derived engineering value Arctic needs on every | ||||||
| # `compute_log_prob` / `update_actor` call. Cached here at init | ||||||
|
|
@@ -159,7 +165,7 @@ def requires_single_forwarder(self) -> bool: | |||||
| # ``processing`` pipeline) and dispatch through the private | ||||||
| # `_send_*` wire helpers below. | ||||||
|
|
||||||
| def compute_log_prob( | ||||||
| async def compute_log_prob( | ||||||
| self, | ||||||
| data, | ||||||
| *, | ||||||
|
|
@@ -182,7 +188,7 @@ def compute_log_prob( | |||||
| ) | ||||||
| payload = dict(batch=batch, meta=meta) | ||||||
|
|
||||||
| response = ( | ||||||
| response = await ( | ||||||
| self._send_compute_ref_log_prob(payload) if ref else self._send_compute_log_prob(payload) | ||||||
| ) | ||||||
|
|
||||||
|
|
@@ -193,7 +199,7 @@ def compute_log_prob( | |||||
| metrics = dict(response.get("metrics", {})) | ||||||
| return {"model_output": model_output, "metrics": metrics} | ||||||
|
|
||||||
| def update_actor( | ||||||
| async def update_actor( | ||||||
| self, | ||||||
| data, | ||||||
| *, | ||||||
|
|
@@ -251,7 +257,7 @@ def _safe_serialize(obj): | |||||
| meta["policy_loss_config"] = _safe_serialize(vars(policy_loss)) if policy_loss is not None else {} | ||||||
|
|
||||||
| payload = dict(batch=batch, meta=meta) | ||||||
| response = self._send_update_actor(payload) | ||||||
| response = await self._send_update_actor(payload) | ||||||
|
|
||||||
| metrics = dict(response["metrics"]) | ||||||
| loss = metrics.pop("loss") | ||||||
|
|
@@ -267,7 +273,7 @@ def _safe_serialize(obj): | |||||
| def _create_ds_config(self, n_gpus: int) -> dict[str, Any]: | ||||||
| actor_cfg = self.config.actor_rollout_ref.actor | ||||||
| data_cfg = self.config.data | ||||||
| zero_optimization_config = self.config.arctic_rl.zero_optimization | ||||||
| zero_optimization_config = self._backend_config.zero_optimization | ||||||
|
|
||||||
| micro_batch_size = actor_cfg.ppo_micro_batch_size_per_gpu or 1 | ||||||
| train_batch_size = data_cfg.train_batch_size * self.config.actor_rollout_ref.rollout.n | ||||||
|
|
@@ -283,7 +289,7 @@ def _create_ds_config(self, n_gpus: int) -> dict[str, Any]: | |||||
| "offload_optimizer": {"device": zero_optimization_config.offload_optimizer.device}, | ||||||
| "offload_param": {"device": zero_optimization_config.offload_param.device}, | ||||||
| }, | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
|
|
||||||
| def reconnect_config(self): | ||||||
|
|
@@ -309,7 +315,7 @@ def _create_ds_worker_config(self): | |||||
| max_token_len=self.config.actor_rollout_ref.rollout.max_num_batched_tokens, | ||||||
| rollout_n=self.config.actor_rollout_ref.rollout.n, | ||||||
| temperature=self.config.actor_rollout_ref.rollout.temperature, | ||||||
| tiled_logits_compute=self.config.arctic_rl.tiled_logits_compute, | ||||||
| tiled_logits_compute=self._backend_config.get("tiled_logits_compute", True), | ||||||
| use_unpad=use_unpad, | ||||||
| ) | ||||||
|
|
||||||
|
|
@@ -324,10 +330,10 @@ def _initialize_client(self, reconnect_job_config: dict = None, rl_server_state: | |||||
| return create_arctic_rl_client(reconnect_job_config) | ||||||
|
|
||||||
| model_name = self.config.actor_rollout_ref.model.path | ||||||
| n_training_gpus = self.config.arctic_rl.get("training_gpus", self.config.trainer.n_gpus_per_node) | ||||||
| n_sampling_gpus = self.config.arctic_rl.get("sampling_gpus", self.config.trainer.n_gpus_per_node) | ||||||
| n_log_prob_gpus = self.config.arctic_rl.get("log_prob_gpus", self.config.trainer.n_gpus_per_node) | ||||||
| colocate = self.config.arctic_rl.get("colocate", False) | ||||||
| n_training_gpus = self._backend_config.get("training_gpus", self.config.trainer.n_gpus_per_node) | ||||||
| n_sampling_gpus = self._backend_config.get("sampling_gpus", self.config.trainer.n_gpus_per_node) | ||||||
| n_log_prob_gpus = self._backend_config.get("log_prob_gpus", self.config.trainer.n_gpus_per_node) | ||||||
| colocate = self._backend_config.get("colocate", True) | ||||||
| attn_implementation = self.config.actor_rollout_ref.model.override_config.get( | ||||||
| 'attn_implementation', 'eager' | ||||||
| ) | ||||||
|
|
@@ -340,7 +346,7 @@ def _initialize_client(self, reconnect_job_config: dict = None, rl_server_state: | |||||
|
|
||||||
| rollout_cfg = self.config.actor_rollout_ref.rollout | ||||||
| vllm_config = { | ||||||
| "tensor_parallel_size": self.config.arctic_rl.sampling_tp_size, | ||||||
| "tensor_parallel_size": self._backend_config.sampling_tp_size, | ||||||
| "gpu_memory_utilization": rollout_cfg.gpu_memory_utilization, | ||||||
| "max_model_len": rollout_cfg.get("max_model_len") or max_length, | ||||||
| "max_num_seqs": rollout_cfg.max_num_seqs, | ||||||
|
|
@@ -351,9 +357,9 @@ def _initialize_client(self, reconnect_job_config: dict = None, rl_server_state: | |||||
| vllm_config["quantization"] = rollout_cfg.quantization | ||||||
|
|
||||||
| rl_config = ArcticRLClientConfig( | ||||||
| host=None if self.config.arctic_rl.comm_protocol == "ray" else "localhost", | ||||||
| port=None if self.config.arctic_rl.comm_protocol == "ray" else 7000, | ||||||
| comm_protocol=self.config.arctic_rl.comm_protocol, | ||||||
| host=None if self._backend_config.comm_protocol == "ray" else "localhost", | ||||||
| port=None if self._backend_config.comm_protocol == "ray" else 7000, | ||||||
| comm_protocol=self._backend_config.comm_protocol, | ||||||
| backend="local", | ||||||
| training_gpus=n_training_gpus, | ||||||
| sampling_gpus=n_sampling_gpus, | ||||||
|
|
@@ -414,19 +420,22 @@ async def generate(self, prompt_ids, sampling_params) -> list: | |||||
| # backends are free to pick their own wire format; verl never calls | ||||||
| # these directly. | ||||||
|
|
||||||
| def _send_compute_ref_log_prob(self, payload: dict): | ||||||
| async def _send_compute_ref_log_prob(self, payload: dict): | ||||||
| payload["processing"] = {"post": ["compute_entropy_and_logprobs"], "loss_fn": None} | ||||||
| response = self._client.fwd_no_grad(payload, reference_model=True) | ||||||
| # Offload the blocking RPC to a worker thread so the forwarder's | ||||||
| # asyncio loop (and the Ray actor that owns it) stays responsive | ||||||
| # to concurrent calls. | ||||||
| response = await asyncio.to_thread(self._client.fwd_no_grad, payload, reference_model=True) | ||||||
| response["batch"]["log_probs"] = response["batch"].pop("logprobs") | ||||||
| return response | ||||||
|
|
||||||
| def _send_compute_log_prob(self, payload: dict): | ||||||
| async def _send_compute_log_prob(self, payload: dict): | ||||||
| payload["processing"] = {"post": ["compute_entropy_and_logprobs"], "loss_fn": None} | ||||||
| response = self._client.fwd_no_grad(payload, reference_model=False) | ||||||
| response = await asyncio.to_thread(self._client.fwd_no_grad, payload, reference_model=False) | ||||||
| response["batch"]["log_probs"] = response["batch"].pop("logprobs") | ||||||
| return response | ||||||
|
|
||||||
| def _send_update_actor(self, payload: dict): | ||||||
| async def _send_update_actor(self, payload: dict): | ||||||
| payload["processing"] = { | ||||||
| "post": ["apply_temperature", "compute_entropy_and_logprobs"], | ||||||
| "loss_fn": "verl_grpo", | ||||||
|
|
@@ -446,13 +455,13 @@ def _left_pad(t: torch.Tensor, seq_len: int) -> torch.Tensor: | |||||
| payload["batch"][name] = _left_pad(payload["batch"][name], seq_len) | ||||||
| payload["batch"]["loss_mask"] = payload["batch"]["response_mask"] | ||||||
|
|
||||||
| fwd_bwd_response = self._client.fwd_bwd(payload) | ||||||
| step_response = self._client.step() | ||||||
| fwd_bwd_response = await asyncio.to_thread(self._client.fwd_bwd, payload) | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just
Suggested change
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as the
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Retry
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done in 8aec8a6 — bumped Smoke (6-step GSM8K GRPO on H200): MFU 186-218, matches the prior |
||||||
| step_response = await asyncio.to_thread(self._client.step) | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ditto
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as the
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Retry
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done in 8aec8a6 — same pass as the |
||||||
| step_response["metrics"].update(**fwd_bwd_response["metrics"]) | ||||||
| return step_response | ||||||
|
|
||||||
| def save_checkpoint(self): | ||||||
| return self._client.save_checkpoint() | ||||||
| async def save_checkpoint(self): | ||||||
| return await self._client.save_checkpoint() | ||||||
|
|
||||||
| async def update_weights(self): | ||||||
| return await self._client.sync_weights() | ||||||
|
|
||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use simpler
awaitUh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tried bare
await self._client.fwd_no_grad(...)first — crashes withTypeError: object dict can't be used in 'await' expression.I checked the latest
arctic_training(HEAD on the verl-integration branch) and verified the runtime signatures:So bare
awaitworks forsave_checkpoint(line 464) but not forfwd_no_grad(this line),fwd_bwd(L458), orstep(L459) — they're still sync upstream.Two paths:
asyncio.to_thread(self._client.fwd_no_grad, ...)so the forwarder's asyncio loop stays unblocked (gemini-bot G3 on [trainer] feat: generic remote backend abstraction for RL Training verl-project/verl#6422 made the same point) and the verl PR ships against the currentarctic_training.fwd_no_grad/fwd_bwd/stepgo async upstream, I dropto_threadin a small follow-up — happy to do that as soon as you point me at the commit.I switched
save_checkpointto bareawaitin 188338b since that one is already async upstream.