Repository navigation
fix(verl): normalize the engine policy loss over the global mini-batch - #641
Open
flyingdoubleG wants to merge 1 commit into
Open
flyingdoubleG wants to merge 1 commit into
flyingdoubleG wants to merge 1 commit into
Conversation
With verl >= 0.8 the actor is trained through verl's engine API, which sums the losses of all micro-batches before the optimizer step (Megatron's per-micro-batch division is undone by the engine) and leaves the normalization to the loss function: verl's own ppo_loss divides by the global token/sequence count and multiplies by dp_size. TrinityPolicyLoss returned a loss normalized within the micro-batch, so the gradient was multiplied by the number of micro-batches per rank, which varies from step to step under dynamic batching. The legacy dp_actor path scaled each micro-batch by its share of the mini-batch; the engine path dropped that. Weight each micro-batch loss by its share of the global mini-batch: tokens for token-mean, sequences for the seq-mean modes and for losses without loss_agg_mode (DPO), and dp_size only for seq-mean-token-sum-norm, which is already a sum. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Problem. With verl ≥ 0.8 (
is_verl_legacy()is False; PyPI currently ships 0.9.1), the actor is trained through verl's engine API usingTrinityPolicyLoss. The engine sums the micro-batch losses:forward_backward_batchcallsloss.backward()for every micro-batch, with no averaging.num_micro_batchto undo Megatron's per-micro-batch division.Normalization is therefore left to the loss function. The engine attaches
batch_num_tokens(global across DP),dp_sizeandglobal_batch_sizeto each micro-batch, and verl's ownppo_lossuses them to return each micro-batch's share of the global mini-batch loss. In verl's words (engine_workers.py): "we should always sum the loss of each micro-batch as we scale by global_bsz/global_token".TrinityPolicyLossreturns a loss normalized within the micro-batch:aggregate_lossis a local token or sequence mean. As a result the actor gradient is multiplied by the number of micro-batches per rank.Under the default dynamic batching (
use_dynamic_bszwithmax_token_len_per_gpu), that number changes from step to step with response length, so steps with longer responses get proportionally larger gradients. Gradient clipping and the loggedgrad_normsee the inflated values too.The legacy path (
verl_legacy/dp_actor.py) does not have this problem: it scales each micro-batch by1/gradient_accumulation, or by its token share. The engine path dropped that scaling.Reproduction on CPU: the real
TrinityPolicyLoss+ verl'sleft_right_2_no_padding, summing micro-batch losses as the engine does. Gradient norm relative to a single micro-batch:mainmainFix.
TrinityPolicyLossnow multiplies the combined loss (pg − entropy + kl) by the micro-batch's share of the global mini-batch, timesdp_size, following the same contract as verl'sppo_loss:token-mean: local response tokens /batch_num_tokens.seq-mean-token-mean,seq-mean-token-sum, and losses withoutloss_agg_mode(DPO): local sequences /global_batch_size. Trinity's_update_actoralready setsglobal_batch_size.seq-mean-token-sum-norm: this is already a sum, so onlydp_sizeis applied, to undo the DP averaging.When the engine does not provide these values (e.g. outside the engine), the weight is 1, so behaviour there is unchanged. Logged loss metrics are unchanged as well, because they use the unscaled value.
How to test. New
tests/trainer/verl_losses_test.pybuilds micro-batches the way the engine does. It checks that the gradient of the summed losses with 2 and 4 micro-batches equals the single-micro-batch gradient, fortoken-meanandseq-mean-token-mean. Onmainit fails with 13/40 elements mismatched (the rest are padding with zero gradient); with this change it passes (1 test, 4 subtests, CPU). I have not run a GPU training job.Checklist
🤖 Generated with Claude Code