33
44import torch
55
6- from fast_llm .engine .base_model .config import LossDef
6+ from fast_llm .core .distributed import ReduceOp , all_reduce
7+ from fast_llm .engine .base_model .config import LossDef , ReductionType
8+ from fast_llm .engine .distributed .config import DistributedConfig
79from fast_llm .functional .config import TritonConfig
810from fast_llm .functional .entropy_loss import fused_predicted_logits_from_labels , fused_softmax_base
911from fast_llm .functional .utils import reduce_losses
1012from fast_llm .layers .language_model .config import LanguageModelKwargs
11- from fast_llm .layers .language_model .loss .config import LanguageModelGRPOLossConfig , LanguageModelLossKwargs
13+ from fast_llm .layers .language_model .loss .config import (
14+ GRPOMetricsLevel ,
15+ LanguageModelGRPOLossConfig ,
16+ LanguageModelLossKwargs ,
17+ )
1218from fast_llm .layers .language_model .loss .loss import LanguageModelLoss
19+ from fast_llm .utils import Assert
20+
21+
22+ class GRPOMetrics (typing .NamedTuple ):
23+ old_logprobs : torch .Tensor
24+ ratio_new_old : torch .Tensor
25+ ratio_new_old_sum : torch .Tensor
26+ ratio_new_old_squared_sum : torch .Tensor
27+ kl_new_old : torch .Tensor
28+ clipped_ratio_fraction : torch .Tensor
29+ advantage : torch .Tensor
30+ max_advantage : torch .Tensor
31+ min_advantage : torch .Tensor
32+ num_tokens : torch .Tensor
33+ entropy : torch .Tensor | None
1334
1435
1536class LanguageModelGRPOLoss [ConfigType : LanguageModelGRPOLossConfig ](LanguageModelLoss [ConfigType ]):
37+ def __init__ (
38+ self ,
39+ config : ConfigType ,
40+ distributed_config : DistributedConfig ,
41+ * ,
42+ name : str ,
43+ prediction_distance : int = 1 ,
44+ prediction_heads : int = 1 ,
45+ vocab_parallel : bool = False ,
46+ num_splits : int = 1 ,
47+ logits_scale_factor : float = 1.0 ,
48+ weight : float = 1.0 ,
49+ register_loss : bool = False ,
50+ ):
51+ super ().__init__ (
52+ config ,
53+ distributed_config ,
54+ name = name ,
55+ prediction_distance = prediction_distance ,
56+ prediction_heads = prediction_heads ,
57+ vocab_parallel = vocab_parallel ,
58+ num_splits = num_splits ,
59+ logits_scale_factor = logits_scale_factor ,
60+ weight = weight ,
61+ register_loss = register_loss ,
62+ )
63+ Assert .custom (
64+ lambda metrics , pipeline_parallel : metrics == GRPOMetricsLevel .none or pipeline_parallel == 1 ,
65+ config .metrics ,
66+ distributed_config .pipeline_parallel ,
67+ )
68+
1669 def _forward_backward (
1770 self ,
1871 logits : "torch.Tensor" ,
@@ -51,10 +104,88 @@ def _forward_backward(
51104 self ._register_loss (
52105 self ._logprob_metric_name , new_logprobs_mean , losses , reduce_op = torch .distributed .ReduceOp .SUM
53106 )
107+
108+ # Skip the extra softmax pass when there is nothing to register.
109+ if losses is not None and self ._config .metrics != GRPOMetricsLevel .none :
110+ self ._register_extra_metrics (logits , kwargs , losses , split_index )
111+
54112 return loss , grad
55113
114+ def _register_extra_metrics (
115+ self ,
116+ logits : torch .Tensor ,
117+ kwargs : dict [str , typing .Any ],
118+ losses : dict | None ,
119+ split_index : int ,
120+ ) -> None :
121+ metrics = compute_grpo_metrics (
122+ logits ,
123+ self ._get_labels (kwargs , split_index ),
124+ self ._prepare_target (kwargs [LanguageModelLossKwargs .advantages ], split_index ),
125+ self ._prepare_target (kwargs [LanguageModelLossKwargs .old_log_probabilities ], split_index ),
126+ self ._prepare_target (kwargs [LanguageModelLossKwargs .label_counts ], split_index ),
127+ self ._config .epsilon_low ,
128+ self ._config .epsilon_high ,
129+ self ._logits_scale_factor ,
130+ group = self ._parallel_dim .group if self ._vocab_parallel else None ,
131+ compute_entropy = self ._config .metrics == GRPOMetricsLevel .with_entropy ,
132+ )
133+
134+ num_documents = kwargs [LanguageModelKwargs .num_documents_in_batch ]
135+
136+ for attr in (
137+ "old_logprobs" ,
138+ "ratio_new_old" ,
139+ "kl_new_old" ,
140+ "clipped_ratio_fraction" ,
141+ "advantage" ,
142+ ):
143+ self ._register_loss (f"{ self ._name } _{ attr } " , getattr (metrics , attr ) / num_documents , losses )
144+
145+ for attr in (
146+ "ratio_new_old_sum" ,
147+ "ratio_new_old_squared_sum" ,
148+ "num_tokens" ,
149+ ):
150+ self ._register_loss (f"{ self ._name } _{ attr } " , getattr (metrics , attr ), losses )
151+
152+ self ._register_loss (
153+ f"{ self ._name } _max_advantage" ,
154+ metrics .max_advantage ,
155+ losses ,
156+ reduce_op = torch .distributed .ReduceOp .MAX ,
157+ )
158+ self ._register_loss (
159+ f"{ self ._name } _min_advantage" ,
160+ metrics .min_advantage ,
161+ losses ,
162+ reduce_op = torch .distributed .ReduceOp .MIN ,
163+ )
164+
165+ if metrics .entropy is not None :
166+ self ._register_loss (f"{ self ._name } _entropy" , metrics .entropy / num_documents , losses )
167+
56168 def get_loss_definitions (self ) -> list [LossDef ]:
57- return super ().get_loss_definitions () + [LossDef (self ._logprob_metric_name )]
169+ defs = super ().get_loss_definitions ()
170+ defs .append (LossDef (self ._logprob_metric_name ))
171+ if self ._config .metrics != GRPOMetricsLevel .none :
172+ defs .extend (
173+ [
174+ LossDef (f"{ self ._name } _old_logprobs" ),
175+ LossDef (f"{ self ._name } _ratio_new_old" ),
176+ LossDef (f"{ self ._name } _ratio_new_old_sum" ),
177+ LossDef (f"{ self ._name } _ratio_new_old_squared_sum" ),
178+ LossDef (f"{ self ._name } _kl_new_old" ),
179+ LossDef (f"{ self ._name } _clipped_ratio_fraction" ),
180+ LossDef (f"{ self ._name } _advantage" ),
181+ LossDef (f"{ self ._name } _max_advantage" , reduction = ReductionType .maximum ),
182+ LossDef (f"{ self ._name } _min_advantage" , reduction = ReductionType .minimum ),
183+ LossDef (f"{ self ._name } _num_tokens" ),
184+ ]
185+ )
186+ if self ._config .metrics == GRPOMetricsLevel .with_entropy :
187+ defs .append (LossDef (f"{ self ._name } _entropy" ))
188+ return defs
58189
59190 def get_preprocessing_config (
60191 self ,
@@ -66,6 +197,61 @@ def _logprob_metric_name(self) -> str:
66197 return f"{ self ._name } _new_logprobs"
67198
68199
200+ @torch .compile
201+ def compute_grpo_metrics (
202+ logits : torch .Tensor , # (*batch, vocab_local)
203+ target : torch .Tensor , # (*batch,)
204+ advantages : torch .Tensor , # (*batch,)
205+ old_log_probabilities : torch .Tensor , # (*batch,)
206+ label_counts : torch .Tensor , # (*batch,) — global per-sequence count broadcast per token
207+ epsilon_low : float = 0.2 ,
208+ epsilon_high : float = 0.2 ,
209+ logits_scale_factor : float = 1.0 ,
210+ group : torch .distributed .ProcessGroup | None = None ,
211+ compute_entropy : bool = False ,
212+ ) -> GRPOMetrics :
213+ loss_mask = target >= 0
214+ mask = loss_mask .float ()
215+ masked = mask / label_counts .float ().clamp (min = 1 )
216+
217+ logits_norm , exp_logits , sum_exp_logits , _ = fused_softmax_base (logits , logits_scale_factor , group )
218+ predicted_logits , _ , _ = fused_predicted_logits_from_labels (logits_norm , target , loss_mask , group )
219+ new_log_probs = predicted_logits - sum_exp_logits .log ()
220+
221+ log_ratio = new_log_probs - old_log_probabilities
222+ ratio = log_ratio .exp ()
223+ clipped = (ratio < 1.0 - epsilon_low ) | (ratio > 1.0 + epsilon_high )
224+ kl = ratio - log_ratio - 1.0
225+
226+ neg_inf = advantages .new_full ((), float ("-inf" ))
227+ pos_inf = advantages .new_full ((), float ("inf" ))
228+
229+ entropy : torch .Tensor | None = None
230+ if compute_entropy :
231+ # exp_logits and logits_norm are local vocab slices — sum over the local slice, then all-reduce
232+ # across the tensor-parallel group to recover the global E_p[logit_norm] before dividing by the
233+ # already-global sum_exp_logits.
234+ weighted_logits_sum = (exp_logits * logits_norm ).sum (- 1 )
235+ if group is not None :
236+ all_reduce (weighted_logits_sum , op = ReduceOp .SUM , group = group )
237+ entropy_per_token = sum_exp_logits .log () - weighted_logits_sum / sum_exp_logits
238+ entropy = (entropy_per_token * masked ).sum ()
239+
240+ return GRPOMetrics (
241+ old_logprobs = (old_log_probabilities * masked ).sum (),
242+ ratio_new_old = (ratio * masked ).sum (),
243+ ratio_new_old_sum = (ratio * mask ).sum (),
244+ ratio_new_old_squared_sum = (ratio * ratio * mask ).sum (),
245+ kl_new_old = (kl * masked ).sum (),
246+ clipped_ratio_fraction = (clipped .float () * masked ).sum (),
247+ advantage = (advantages * masked ).sum (),
248+ max_advantage = torch .where (loss_mask , advantages , neg_inf ).max (),
249+ min_advantage = torch .where (loss_mask , advantages , pos_inf ).min (),
250+ num_tokens = mask .sum (),
251+ entropy = entropy ,
252+ )
253+
254+
69255@torch .compile
70256def fused_grpo_loss_forward_backward (
71257 logits : torch .Tensor , # (*batch, vocab)
0 commit comments