Repository navigation
Conversation
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
The docs for this PR live here. All of your documentation changes will be reflected on that endpoint. The docs are available until 30 days after the last update. |
# Conflicts: # trl/experimental/async_grpo/async_grpo_trainer.py
e2fe8a3 to
c99ce3b
Compare
# Conflicts: # trl/experimental/async_grpo/async_grpo_trainer.py
|
Thanks @kashif, and nice to see Snowflake-AI-Research/Arctic-Platform#84 running end to end on it. I'd rather not merge this as is. AsyncGRPO is about to move out of experimental (@AmineDiro), and once it does, its constructor and protocols are public API we can't change freely. A third protocol plus Most of it can (and should IMO) live on the Arctic side:
The one real blocker is that So my proposal: shrink this PR / new PR to accepting an instantiated model, and keep the client in Arctic Platform. Rough sketch of your side (untested): class RemoteModel(nn.Module):
# Carries the config the trainer reads, and one parameter for Trainer/accelerate to place
def __init__(self, config):
super().__init__()
self.config = config
self.generation_config = GenerationConfig.from_model_config(config)
self.placeholder = nn.Parameter(torch.zeros(1))
@property
def dtype(self): # read by the trainer's MFU setup
return self.placeholder.dtype
class RemoteOptimizer(torch.optim.Optimizer):
def __init__(self, params, client, lr):
super().__init__(params, {"lr": lr})
self.client = client
def step(self, closure=None):
# The trainer's scheduler writes the scheduled lr into param_groups
self.client.optimizer_step(self.param_groups[0]["lr"])
class ArcticAsyncGRPOTrainer(AsyncGRPOTrainer):
def __init__(self, *args, client, **kwargs):
self.client = client
super().__init__(*args, **kwargs)
def compute_loss(self, model, inputs, return_outputs=False, num_items_in_batch=None):
input_ids, position_ids, completion_mask, old_log_probs, advantages = ... # same unpacking as the parent
# Forward on the backend, log probs come back detached
log_probs = self.client.forward(input_ids, position_ids, completion_mask).requires_grad_()
# On backward, ship d(loss)/d(log_probs) to the backend, which backprops sum(grad * log_probs)
log_probs.register_hook(self.client.backward)
loss = ... # same GRPO loss as the parent, on log_probs
return loss
def save_model(self, output_dir=None, _internal_call=False):
output_dir = output_dir or self.args.output_dir
self.client.save(output_dir)
self.processing_class.save_pretrained(output_dir)
def _load_from_checkpoint(self, resume_from_checkpoint, model=None):
self.client.load(resume_from_checkpoint)
client = ArcticClient(...)
model = RemoteModel(AutoConfig.from_pretrained("Qwen/Qwen3-8B"))
args = AsyncGRPOConfig(..., gradient_checkpointing=False)
trainer = ArcticAsyncGRPOTrainer(
model=model,
client=client,
args=args,
reward_funcs=...,
train_dataset=...,
optimizers=(RemoteOptimizer(model.parameters(), client, args.learning_rate), None),
weight_transfer=ArcticWeightTransfer(client), # syncs vLLM from the backend, ignores the iterator
)
trainer.train()If the backend computes the loss next to the model instead, the same |
# Conflicts: # tests/experimental/test_async_grpo_trainer.py
qgallouedec
left a comment
There was a problem hiding this comment.
Thks, that's the shape I had in mind. A few things to align with the other trainers:
- Non-string branch, like GRPO:
and drop the
else: model_revision = None if args.model_init_kwargs is not None: logger.warning( "You passed `model_init_kwargs` to the `AsyncGRPOConfig`, but your model is already instantiated. " "The `model_init_kwargs` will be ignored." )
model_revision = Noneabove theif. - Type hint
model: "str | PreTrainedModel", and in the docstringmodel (stror [~transformers.PreTrainedModel])with a second bullet- A [~transformers.PreTrainedModel] object.(plus your sentence on the padding-free attention requirement). No other trainer liststorch.nn.Module. - Tests: keep
test_trainon the model id and add atest_train_modelthat passes an instance, liketests/test_sft_trainer.py::test_train_model.
qgallouedec
left a comment
There was a problem hiding this comment.
otherwise lgtm, cc @AmineDiro
# Conflicts: # trl/experimental/async_grpo/async_grpo_trainer.py
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Want higher recall? High effort reviews run extra passes and find more bugs. A team admin can switch effort levels in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 64ec7b2. Configure here.
AmineDiro
left a comment
There was a problem hiding this comment.
With tree attention integrated this actually complicates the integration with some remotre training model, because the remote trainer will probably need to understand the shape of the data it gets OR its a completely different path and builds its own rollout worker API on to of the trainer.
|
I reviewed the current head ( 1. Lightweight remote-model placeholders now fail in
|
|
Thanks @sfc-gh-truwase. Neither point needs a hook in TRL.
The placeholder should be a
A no-op on your placeholder class leaves local models alone: they keep the real method. Only a
Tree packing is the batch contract since #7328, and there is no flat path left. On your side, either run TRL's |

AsyncGRPOTrainercurrently requiresmodelto be a model ID and always loads the weights itself. This PR brings that constructor argument in line with the other trainers by also accepting an instantiatedPreTrainedModelortorch.nn.Module.This is the one missing primitive needed by external training backends such as Arctic Platform #84: an integration can pass a lightweight module carrying the model config, then keep its remote loss, optimizer, checkpointing, and weight-transfer behavior in its own subclass using the hooks that already exist.
Behavior
model_init_kwargsare ignored with a warning for an instantiated model, matching the established trainer behavior.argsis omitted, the default output name is derived from the instantiated model's config.Verification
The existing end-to-end training test now passes a real preloaded tiny model, verifies that the trainer keeps that exact instance, trains it, and verifies that its parameters change. The existing model-ID construction test remains in place.
Scope
This PR intentionally does not add a training-client protocol, remote model/optimizer implementations, remote checkpoint handling, or remote-loss branches to TRL. It changes only
AsyncGRPOTrainerand its test; external backends own those integration-specific pieces.Note
Medium Risk
Constructor and model-loading paths diverge for string vs instance; wrong attention or ignored kwargs could affect training, but scope is limited to AsyncGRPOTrainer init with existing tree-attention checks unchanged for loaded models.
Overview
AsyncGRPOTrainernow accepts either a Hugging Face model id/path or an already-loadedPreTrainedModel, matching other TRL trainers.For a string, behavior is unchanged: the trainer loads via
create_model_from_pathwith tree attention, andmodel_init_kwargsis copied so the config dict is not mutated. For an instantiated model, that object is used as-is, attention is switched toTREE_ATTENTIONfor packed training, andmodel_init_kwargsis ignored with a warning. Whenargsis omitted, the default run name comes from the model config (viaget_config_model_id) instead of splitting a path string.A new integration test
test_train_modelpreloads a tiny Qwen model, checks identity and tree attention, runstrain(), and asserts weights update—alongside the existing model-idtest_train.Reviewed by Cursor Bugbot for commit e7f6928. Bugbot is set up for automated code reviews on this repo. Configure here.