cciwon-code-review-cli 2.0.1 → 2.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/bin/code-review.js +1 -1
  2. package/lib/chat-mode.js +7 -2
  3. package/package.json +1 -1
  4. package/unsloth_compiled_cache/.locks/.lock.AqlmLoraLinear_peft_forward.py +0 -0
  5. package/unsloth_compiled_cache/.locks/.lock.AwqLoraLinear_peft_forward.py +0 -0
  6. package/unsloth_compiled_cache/.locks/.lock.BatchNorm1d.py +0 -0
  7. package/unsloth_compiled_cache/.locks/.lock.BatchNorm2d.py +0 -0
  8. package/unsloth_compiled_cache/.locks/.lock.BatchNorm3d.py +0 -0
  9. package/unsloth_compiled_cache/.locks/.lock.Conv1d.py +0 -0
  10. package/unsloth_compiled_cache/.locks/.lock.Conv2d.py +0 -0
  11. package/unsloth_compiled_cache/.locks/.lock.Conv3d.py +0 -0
  12. package/unsloth_compiled_cache/.locks/.lock.ConvTranspose1d.py +0 -0
  13. package/unsloth_compiled_cache/.locks/.lock.ConvTranspose2d.py +0 -0
  14. package/unsloth_compiled_cache/.locks/.lock.ConvTranspose3d.py +0 -0
  15. package/unsloth_compiled_cache/.locks/.lock.GPTQLoraLinear_peft_forward.py +0 -0
  16. package/unsloth_compiled_cache/.locks/.lock.GroupNorm.py +0 -0
  17. package/unsloth_compiled_cache/.locks/.lock.LayerNorm.py +0 -0
  18. package/unsloth_compiled_cache/.locks/.lock.Linear4bit_peft_forward.py +0 -0
  19. package/unsloth_compiled_cache/.locks/.lock.Linear8bitLt_peft_forward.py +0 -0
  20. package/unsloth_compiled_cache/.locks/.lock.Linear_peft_forward.py +0 -0
  21. package/unsloth_compiled_cache/.locks/.lock.LoraParallelLinear_peft_forward.py +0 -0
  22. package/unsloth_compiled_cache/.locks/.lock.RMSNorm.py +0 -0
  23. package/unsloth_compiled_cache/.locks/.lock.UnslothBCOTrainer.py +0 -0
  24. package/unsloth_compiled_cache/.locks/.lock.UnslothCPOTrainer.py +0 -0
  25. package/unsloth_compiled_cache/.locks/.lock.UnslothDPOTrainer.py +0 -0
  26. package/unsloth_compiled_cache/.locks/.lock.UnslothGKDTrainer.py +0 -0
  27. package/unsloth_compiled_cache/.locks/.lock.UnslothGRPOTrainer.py +0 -0
  28. package/unsloth_compiled_cache/.locks/.lock.UnslothKTOTrainer.py +0 -0
  29. package/unsloth_compiled_cache/.locks/.lock.UnslothNashMDTrainer.py +0 -0
  30. package/unsloth_compiled_cache/.locks/.lock.UnslothORPOTrainer.py +0 -0
  31. package/unsloth_compiled_cache/.locks/.lock.UnslothOnlineDPOTrainer.py +0 -0
  32. package/unsloth_compiled_cache/.locks/.lock.UnslothPPOTrainer.py +0 -0
  33. package/unsloth_compiled_cache/.locks/.lock.UnslothPRMTrainer.py +0 -0
  34. package/unsloth_compiled_cache/.locks/.lock.UnslothRLOOTrainer.py +0 -0
  35. package/unsloth_compiled_cache/.locks/.lock.UnslothRewardTrainer.py +0 -0
  36. package/unsloth_compiled_cache/.locks/.lock.UnslothSFTTrainer.py +0 -0
  37. package/unsloth_compiled_cache/.locks/.lock.UnslothXPOTrainer.py +0 -0
  38. package/unsloth_compiled_cache/.locks/.lock.unsloth_compiled_module_qwen3_moe.py +0 -0
  39. package/unsloth_compiled_cache/.locks/.lock.unsloth_compiled_module_siglip.py +0 -0
  40. package/unsloth_compiled_cache/AqlmLoraLinear_peft_forward.py +88 -0
  41. package/unsloth_compiled_cache/AwqLoraLinear_peft_forward.py +87 -0
  42. package/unsloth_compiled_cache/BatchNorm1d.py +117 -0
  43. package/unsloth_compiled_cache/BatchNorm2d.py +117 -0
  44. package/unsloth_compiled_cache/BatchNorm3d.py +117 -0
  45. package/unsloth_compiled_cache/Conv1d.py +70 -0
  46. package/unsloth_compiled_cache/Conv2d.py +70 -0
  47. package/unsloth_compiled_cache/Conv3d.py +70 -0
  48. package/unsloth_compiled_cache/ConvTranspose1d.py +97 -0
  49. package/unsloth_compiled_cache/ConvTranspose2d.py +106 -0
  50. package/unsloth_compiled_cache/ConvTranspose3d.py +98 -0
  51. package/unsloth_compiled_cache/GPTQLoraLinear_peft_forward.py +95 -0
  52. package/unsloth_compiled_cache/GroupNorm.py +70 -0
  53. package/unsloth_compiled_cache/LayerNorm.py +72 -0
  54. package/unsloth_compiled_cache/Linear4bit_peft_forward.py +115 -0
  55. package/unsloth_compiled_cache/Linear8bitLt_peft_forward.py +113 -0
  56. package/unsloth_compiled_cache/Linear_peft_forward.py +104 -0
  57. package/unsloth_compiled_cache/LoraParallelLinear_peft_forward.py +91 -0
  58. package/unsloth_compiled_cache/RMSNorm.py +73 -0
  59. package/unsloth_compiled_cache/UnslothBCOTrainer.py +2026 -0
  60. package/unsloth_compiled_cache/UnslothCPOTrainer.py +1806 -0
  61. package/unsloth_compiled_cache/UnslothDPOTrainer.py +2750 -0
  62. package/unsloth_compiled_cache/UnslothGKDTrainer.py +1157 -0
  63. package/unsloth_compiled_cache/UnslothGRPOTrainer.py +3607 -0
  64. package/unsloth_compiled_cache/UnslothKTOTrainer.py +2220 -0
  65. package/unsloth_compiled_cache/UnslothNashMDTrainer.py +1210 -0
  66. package/unsloth_compiled_cache/UnslothORPOTrainer.py +1730 -0
  67. package/unsloth_compiled_cache/UnslothOnlineDPOTrainer.py +2313 -0
  68. package/unsloth_compiled_cache/UnslothPPOTrainer.py +1504 -0
  69. package/unsloth_compiled_cache/UnslothPRMTrainer.py +979 -0
  70. package/unsloth_compiled_cache/UnslothRLOOTrainer.py +2674 -0
  71. package/unsloth_compiled_cache/UnslothRewardTrainer.py +1197 -0
  72. package/unsloth_compiled_cache/UnslothSFTTrainer.py +1416 -0
  73. package/unsloth_compiled_cache/UnslothXPOTrainer.py +1255 -0
  74. package/unsloth_compiled_cache/__pycache__/AqlmLoraLinear_peft_forward.cpython-312.pyc +0 -0
  75. package/unsloth_compiled_cache/__pycache__/AwqLoraLinear_peft_forward.cpython-312.pyc +0 -0
  76. package/unsloth_compiled_cache/__pycache__/BatchNorm1d.cpython-312.pyc +0 -0
  77. package/unsloth_compiled_cache/__pycache__/BatchNorm2d.cpython-312.pyc +0 -0
  78. package/unsloth_compiled_cache/__pycache__/BatchNorm3d.cpython-312.pyc +0 -0
  79. package/unsloth_compiled_cache/__pycache__/Conv1d.cpython-312.pyc +0 -0
  80. package/unsloth_compiled_cache/__pycache__/Conv2d.cpython-312.pyc +0 -0
  81. package/unsloth_compiled_cache/__pycache__/Conv3d.cpython-312.pyc +0 -0
  82. package/unsloth_compiled_cache/__pycache__/ConvTranspose1d.cpython-312.pyc +0 -0
  83. package/unsloth_compiled_cache/__pycache__/ConvTranspose2d.cpython-312.pyc +0 -0
  84. package/unsloth_compiled_cache/__pycache__/ConvTranspose3d.cpython-312.pyc +0 -0
  85. package/unsloth_compiled_cache/__pycache__/GPTQLoraLinear_peft_forward.cpython-312.pyc +0 -0
  86. package/unsloth_compiled_cache/__pycache__/GroupNorm.cpython-312.pyc +0 -0
  87. package/unsloth_compiled_cache/__pycache__/LayerNorm.cpython-312.pyc +0 -0
  88. package/unsloth_compiled_cache/__pycache__/Linear4bit_peft_forward.cpython-312.pyc +0 -0
  89. package/unsloth_compiled_cache/__pycache__/Linear8bitLt_peft_forward.cpython-312.pyc +0 -0
  90. package/unsloth_compiled_cache/__pycache__/Linear_peft_forward.cpython-312.pyc +0 -0
  91. package/unsloth_compiled_cache/__pycache__/LoraParallelLinear_peft_forward.cpython-312.pyc +0 -0
  92. package/unsloth_compiled_cache/__pycache__/RMSNorm.cpython-312.pyc +0 -0
  93. package/unsloth_compiled_cache/__pycache__/UnslothBCOTrainer.cpython-312.pyc +0 -0
  94. package/unsloth_compiled_cache/__pycache__/UnslothCPOTrainer.cpython-312.pyc +0 -0
  95. package/unsloth_compiled_cache/__pycache__/UnslothDPOTrainer.cpython-312.pyc +0 -0
  96. package/unsloth_compiled_cache/__pycache__/UnslothGKDTrainer.cpython-312.pyc +0 -0
  97. package/unsloth_compiled_cache/__pycache__/UnslothGRPOTrainer.cpython-312.pyc +0 -0
  98. package/unsloth_compiled_cache/__pycache__/UnslothKTOTrainer.cpython-312.pyc +0 -0
  99. package/unsloth_compiled_cache/__pycache__/UnslothNashMDTrainer.cpython-312.pyc +0 -0
  100. package/unsloth_compiled_cache/__pycache__/UnslothORPOTrainer.cpython-312.pyc +0 -0
  101. package/unsloth_compiled_cache/__pycache__/UnslothOnlineDPOTrainer.cpython-312.pyc +0 -0
  102. package/unsloth_compiled_cache/__pycache__/UnslothPPOTrainer.cpython-312.pyc +0 -0
  103. package/unsloth_compiled_cache/__pycache__/UnslothPRMTrainer.cpython-312.pyc +0 -0
  104. package/unsloth_compiled_cache/__pycache__/UnslothRLOOTrainer.cpython-312.pyc +0 -0
  105. package/unsloth_compiled_cache/__pycache__/UnslothRewardTrainer.cpython-312.pyc +0 -0
  106. package/unsloth_compiled_cache/__pycache__/UnslothSFTTrainer.cpython-312.pyc +0 -0
  107. package/unsloth_compiled_cache/__pycache__/UnslothXPOTrainer.cpython-312.pyc +0 -0
  108. package/unsloth_compiled_cache/__pycache__/unsloth_compiled_module_qwen3_moe.cpython-312.pyc +0 -0
  109. package/unsloth_compiled_cache/__pycache__/unsloth_compiled_module_siglip.cpython-312.pyc +0 -0
  110. package/unsloth_compiled_cache/unsloth_compiled_module_qwen3_moe.py +726 -0
  111. package/unsloth_compiled_cache/unsloth_compiled_module_siglip.py +534 -0
@@ -0,0 +1,3607 @@
1
+ """
2
+ 2025.12.6
3
+ 2025.12.7
4
+ 4.57.1
5
+ 0.24.0
6
+ __UNSLOTH_VERSIONING__
7
+ """
8
+
9
+ # Unsloth auto generated code
10
+ # Copyright 2023-present Daniel Han-Chen, Michael Han-Chen & the Unsloth team. All rights reserved.
11
+ #
12
+ # This program is free software: you can redistribute it and/or modify
13
+ # it under the terms of the GNU Lesser General Public License as published by
14
+ # the Free Software Foundation, either version 3 of the License, or
15
+ # (at your option) any later version.
16
+ #
17
+ # This program is distributed in the hope that it will be useful,
18
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
19
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20
+ # GNU General Public License for more details.
21
+ #
22
+ # You should have received a copy of the GNU Lesser General Public License
23
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
24
+
25
+ from torch import Tensor
26
+ import torch
27
+ import torch.nn as nn
28
+ from torch.nn import functional as F
29
+ from typing import Any, List, Optional, Tuple, Union, Dict, Set, Callable
30
+ from trl.trainer.grpo_trainer import (Any, AutoConfig, AutoModelForSequenceClassification, AutoProcessor, AutoTokenizer, BaseTrainer, DataLoader, Dataset, FSDP, GRPOConfig, GRPOTrainer, GenerationConfig, GuidedDecodingParams, IterableDataset, LLM, Optional, Path, PeftConfig, PreTrainedModel, PreTrainedTokenizerBase, ProcessorMixin, RepeatSampler, RewardFunc, Sampler, SamplingParams, SyncRefModelCallback, TrainerCallback, Union, VLLMClient, _ForwardRedirection, apply_chat_template, broadcast_object_list, datasets, defaultdict, deque, disable_dropout_in_model, ensure_master_addr_port, entropy_from_logits, gather, gather_object, identity, inspect, is_conversational, is_datasets_available, is_flash_attn_2_available, is_liger_kernel_available, is_peft_model, is_rich_available, is_vllm_available, logger, logging, maybe_apply_chat_template, nanmax, nanmin, nanstd, nn, nullcontext, os, pad, partial, prepare_deepspeed, prepare_fsdp, prepare_multimodal_messages, prepare_peft_model, print_prompt_completions_sample, profiling_context, profiling_decorator, seed_worker, selective_log_softmax, set_seed, shuffle_sequence_dict, split_pixel_values_by_grid, split_tensor_dict, textwrap, torch, transformers, unsplit_pixel_values_by_grid, unwrap_model_for_generation, Any, LLM, Union, gather, gather_object, is_conversational, logging, nanmax, nanmin, nanstd, os, pad, torch, FSDP, GuidedDecodingParams, LLM, Optional, SamplingParams, apply_chat_template, broadcast_object_list, gather, gather_object, is_flash_attn_2_available, maybe_apply_chat_template, nullcontext, os, pad, prepare_multimodal_messages, profiling_context, torch, transformers, unwrap_model_for_generation, entropy_from_logits, os, pad, selective_log_softmax, torch, transformers, Any, Union, profiling_decorator, shuffle_sequence_dict, split_pixel_values_by_grid, split_tensor_dict, torch, unsplit_pixel_values_by_grid, Optional, PreTrainedModel, logger, os, torch, FSDP, LLM, nn, os, FSDP, nn, torch, GRPOTrainer, gather, nanmax, nanmin, os, torch)
31
+
32
+
33
+ import os
34
+ from typing import *
35
+ from dataclasses import dataclass, field
36
+ from packaging.version import Version
37
+ import torch
38
+ import numpy as np
39
+ from contextlib import nullcontext
40
+ from torch.nn import functional as F
41
+ import inspect
42
+ from transformers import DataCollatorForSeq2Seq, DataCollatorForLanguageModeling as TransformersDataCollatorForLanguageModeling
43
+ from transformers.training_args import ParallelMode
44
+
45
+ # Wrap trainer with padding to right and enable training mode
46
+ # Also patches W&B since multiple runs must use wandb.finish()
47
+ import functools
48
+ from types import MethodType
49
+ def prepare_for_training_mode(f):
50
+ @functools.wraps(f)
51
+ def wrapper(self, *args, **kwargs):
52
+ # Enable training mode
53
+ if hasattr(self, 'model') and hasattr(self.model, "for_training"):
54
+ self.model.for_training()
55
+ output = f(self, *args, **kwargs)
56
+ # Return inference mode
57
+ if hasattr(self, 'model') and hasattr(self.model, "for_inference"):
58
+ self.model.for_inference()
59
+ # Patch W&B to enable logging on future runs, otherwise it'll overwrite the first run
60
+ try:
61
+ import wandb
62
+ wandb.finish()
63
+ except:
64
+ pass
65
+ return output
66
+ return wrapper
67
+ pass
68
+
69
+ torch_compile_options = {
70
+ "epilogue_fusion" : True,
71
+ "max_autotune" : False,
72
+ "shape_padding" : True,
73
+ "trace.enabled" : False,
74
+ "triton.cudagraphs" : False,
75
+ }
76
+
77
+ @torch.compile(dynamic = True, fullgraph = True, options = torch_compile_options,)
78
+ def chunked_selective_log_softmax(logits, index):
79
+ # Split into 4 chunks only
80
+ chunked_logits = torch.chunk(logits.reshape(-1, logits.shape[-1]), chunks = 4, dim = 0)
81
+ chunked_index = torch.chunk(index.reshape(-1), chunks = 4, dim = 0)
82
+ all_per_token_logps = []
83
+ # Below loop does the same as selective_log_softmax(chunk_logits, chunk_index)
84
+ for chunk_logits, chunk_index in zip(chunked_logits, chunked_index):
85
+ chunk_logits = chunk_logits.to(torch.float32)
86
+ selected_logits = torch.gather(chunk_logits, dim = -1, index = chunk_index.unsqueeze(-1)).squeeze(-1)
87
+ logsumexp_values = torch.logsumexp(chunk_logits, dim = -1)
88
+ per_token_logps = selected_logits - logsumexp_values
89
+ all_per_token_logps.append(per_token_logps)
90
+ pass
91
+ all_per_token_logps = torch.concat(all_per_token_logps)
92
+ all_per_token_logps = all_per_token_logps.reshape((logits.shape[0], logits.shape[1]))
93
+ return all_per_token_logps
94
+
95
+ def calculate_pad_tokens_in_prompt(
96
+ input_ids: torch.Tensor,
97
+ logits_to_keep: int,
98
+ pad_token_id: int
99
+ ) -> torch.Tensor:
100
+ """
101
+ Given prompt tensor, it returns all the left padded tokens in that sequence. so [pad, pad, pad, cat] = 3 tokens
102
+ """
103
+ if logits_to_keep >= input_ids.shape[1]:
104
+ raise ValueError("logits_to_keep must be smaller than the sequence length.")
105
+
106
+ prompt_section = input_ids[:, :-logits_to_keep]
107
+
108
+ padding_mask = (prompt_section == pad_token_id)
109
+
110
+ pad_token_counts = padding_mask.sum(dim=1)
111
+
112
+ return pad_token_counts
113
+
114
+ def create_completion_attention_mask(
115
+ completion_input_ids: torch.Tensor,
116
+ left_pad_tokens_per_prompt: torch.Tensor,
117
+ max_left_pad: int,
118
+ pad_token_id: int
119
+ ) -> torch.Tensor:
120
+ """
121
+ Given that we have a sequence, [p,p,p,c,c,c,pad,pad,pad]
122
+
123
+ Where p are extra prompt tokens we got from slicing the torch tensor, c is completion tokens
124
+ and pad are pad tokens, this function would make a completion mask that would 0 out the pad
125
+ and p tokens. so in this example [0,0,0,1,1,1,0,0,0]
126
+ """
127
+ batch_size, completion_len = completion_input_ids.shape
128
+ device = completion_input_ids.device
129
+
130
+ num_tokens_to_mask = max_left_pad - left_pad_tokens_per_prompt
131
+
132
+ indices = torch.arange(completion_len, device=device).unsqueeze(0)
133
+ shift_mask = indices >= num_tokens_to_mask.unsqueeze(1)
134
+
135
+ non_padding_mask = (completion_input_ids != pad_token_id)
136
+
137
+ final_mask = shift_mask & non_padding_mask
138
+
139
+ return final_mask
140
+
141
+ def left_pack_padding(tensor: torch.Tensor, pad_id: int) -> torch.Tensor:
142
+ """
143
+ Moves all padding tokens in each sequence of a batch to the right.
144
+ """
145
+ mask = (tensor != pad_id)
146
+ # Must do stable=True since binary mark is unordered
147
+ sorted_indices = torch.argsort(mask, dim=1, descending=True, stable=True)
148
+ packed_tensor = torch.gather(tensor, 1, sorted_indices)
149
+ return packed_tensor
150
+
151
+ def align_logprobs_with_mask(
152
+ logprob_tensor: torch.Tensor,
153
+ attention_mask: torch.Tensor,
154
+ pad_value: float = 0.0
155
+ ) -> torch.Tensor:
156
+ """
157
+ Aligns a log probability tensor with a given attention mask.
158
+ """
159
+
160
+ device = logprob_tensor.device
161
+ batch_size, logprob_seq_len = logprob_tensor.shape
162
+ mask_seq_len = attention_mask.shape[1]
163
+
164
+ padded_logprobs = torch.full(
165
+ attention_mask.shape,
166
+ fill_value=pad_value,
167
+ dtype=logprob_tensor.dtype,
168
+ device=device
169
+ )
170
+
171
+ left_pad_counts = torch.argmax(attention_mask, dim=1)
172
+
173
+ cols = torch.arange(logprob_seq_len, device=device)
174
+ dest_indices = left_pad_counts.unsqueeze(1) + cols
175
+
176
+ # Create destination row indices
177
+ # Shape: [batch_size, logprob_seq_len]
178
+ row_indices = torch.arange(batch_size, device=device).unsqueeze(1).expand_as(dest_indices)
179
+
180
+ # --- 4. Filter out-of-bounds indices and perform assignment ---
181
+ # Create a mask to identify only the indices that are within the bounds
182
+ # of the target tensor's sequence length.
183
+ valid_mask = dest_indices < mask_seq_len
184
+
185
+ # Use this mask to select only the valid row indices, column indices,
186
+ # and the corresponding values from the logprob tensor.
187
+ # This flattens the selected elements into 1D tensors.
188
+ valid_rows = row_indices[valid_mask]
189
+ valid_cols = dest_indices[valid_mask]
190
+ valid_vals = logprob_tensor[valid_mask]
191
+
192
+ # Place the valid values into their correct positions in the padded tensor
193
+ # using a single, efficient advanced indexing operation.
194
+ padded_logprobs[valid_rows, valid_cols] = valid_vals
195
+
196
+ return padded_logprobs
197
+ def grpo_compute_loss(
198
+ ref_logits,
199
+ new_logits,
200
+ old_logits,
201
+ sampling_per_token_logps,
202
+ input_ids,
203
+ mask,
204
+ beta,
205
+ advantages,
206
+ **kwargs
207
+ ):
208
+ # All Unsloth Zoo code licensed under LGPLv3
209
+ # Set defaults for optional arguments
210
+ loss_type = kwargs.get("loss_type", "grpo")
211
+ epsilon_low = kwargs.get("epsilon_low", 0.2)
212
+ epsilon_high = kwargs.get("epsilon_high", 0.2)
213
+ max_completion_length = kwargs.get("max_completion_length", 8192)
214
+ delta = kwargs.get("delta", None)
215
+ temperature = kwargs.get("temperature", 1.0)
216
+ logit_scale_multiply = kwargs.get("logit_scale_multiply", 0.0)
217
+ logit_scale_divide = kwargs.get("logit_scale_divide", 0.0)
218
+ logit_softcapping = kwargs.get("logit_softcapping", 0.0)
219
+ importance_sampling_level = kwargs.get("importance_sampling_level", "token")
220
+ num_items_in_batch = kwargs.get("num_items_in_batch", None)
221
+ current_gradient_accumulation_steps = kwargs.get("current_gradient_accumulation_steps", 1)
222
+ num_processes = kwargs.get("num_processes", 1)
223
+ use_vllm = kwargs.get("use_vllm", False)
224
+ vllm_importance_sampling_cap = kwargs.get("vllm_importance_sampling_cap", 2.0)
225
+ input_ids = input_ids.unsqueeze(-1)
226
+
227
+ # Optional logit softcapping and logit dividing
228
+ if logit_scale_multiply != 0: new_logits = new_logits * logit_scale_multiply
229
+ if logit_scale_divide != 0: new_logits = new_logits / logit_scale_divide
230
+ if logit_softcapping != 0: new_logits = new_logits * torch.tanh(new_logits / logit_softcapping)
231
+
232
+ new_logits = new_logits.to(torch.float32)
233
+ # See https://huggingface.co/blog/the_n_implementation_details_of_rlhf_with_ppo#policy-training-implementation-details
234
+ if temperature != 1.0: new_logits = new_logits / temperature
235
+ new_x = torch.gather(new_logits, dim = -1, index = input_ids).squeeze(-1)
236
+ new = new_x - torch.logsumexp(new_logits, dim = -1)
237
+ # x_i - logsumexp(x_i)
238
+ with torch.no_grad():
239
+ if beta != 0.0:
240
+ assert ref_logits is not None, "ref_logits should not be None when beta != 0.0"
241
+
242
+ # Optional logit softcapping and logit dividing
243
+ if logit_scale_multiply != 0: ref_logits = ref_logits * logit_scale_multiply
244
+ if logit_scale_divide != 0: ref_logits = ref_logits / logit_scale_divide
245
+ if logit_softcapping != 0: ref_logits = ref_logits * torch.tanh(ref_logits / logit_softcapping)
246
+
247
+ ref_logits = ref_logits.to(torch.float32)
248
+ # See https://huggingface.co/blog/the_n_implementation_details_of_rlhf_with_ppo#policy-training-implementation-details
249
+ if temperature != 1.0: ref_logits = ref_logits / temperature
250
+ ref_x = torch.gather(ref_logits, dim = -1, index = input_ids).squeeze(-1)
251
+ ref = ref_x - torch.logsumexp(ref_logits, dim = -1)
252
+ pass
253
+
254
+ if old_logits is not None:
255
+ # Optional logit softcapping and logit dividing
256
+ if logit_scale_multiply != 0: old_logits = old_logits * logit_scale_multiply
257
+ if logit_scale_divide != 0: old_logits = old_logits / logit_scale_divide
258
+ if logit_softcapping != 0: old_logits = old_logits * torch.tanh(old_logits / logit_softcapping)
259
+
260
+ old_logits = old_logits.to(torch.float32)
261
+ # See https://huggingface.co/blog/the_n_implementation_details_of_rlhf_with_ppo#policy-training-implementation-details
262
+ if temperature != 1.0: old_logits = old_logits / temperature
263
+ old_x = torch.gather(old_logits, dim = -1, index = input_ids).squeeze(-1)
264
+ old = old_x - torch.logsumexp(old_logits, dim = -1)
265
+ pass
266
+ if use_vllm and sampling_per_token_logps is not None:
267
+ #must filter out extra prompt tokens in begining after making input_ids left padded
268
+ importance_sampling_ratio = torch.exp((old * mask) - sampling_per_token_logps)
269
+ importance_sampling_ratio = torch.clamp(
270
+ importance_sampling_ratio, max=vllm_importance_sampling_cap
271
+ )
272
+ pass
273
+ pass
274
+
275
+ # Reverse KL
276
+ # Note that this is a low variance low bias estimator for the KL divergence as used in GRPO paper
277
+ if beta != 0.0:
278
+ kl_i = torch.exp(ref - new) - (ref - new) - 1.0
279
+
280
+ else:
281
+ # set kl_i to a tensor of zeros with the correct shape
282
+ if importance_sampling_level == "sequence":
283
+ kl_i = new.new_zeros(new.size(0), 1)
284
+ else:
285
+ kl_i = torch.zeros_like(new)
286
+ # Full correct reverse KL divergence?? Missing term maybe?
287
+ # kl_i = torch.exp(new) * kl_i
288
+
289
+ # Below is forward KL (normal KL)
290
+ # kl_i = torch.exp(old) * (old - new)
291
+ if old_logits is not None:
292
+ log_ratio = new - old
293
+ else:
294
+ log_ratio = new - new.detach()
295
+
296
+ if importance_sampling_level == "token":
297
+ log_importance_weights = log_ratio
298
+ elif importance_sampling_level == "sequence":
299
+ log_importance_weights = (log_ratio * mask).sum(-1) / mask.sum(-1).clamp(min=1.0)
300
+ log_importance_weights = log_importance_weights.unsqueeze(-1)
301
+ else:
302
+ raise ValueError(
303
+ f"Unknown importance sampling level: {importance_sampling_level}. Possible values are 'token' "
304
+ "and 'sequence'."
305
+ )
306
+
307
+ coef_1 = torch.exp(log_importance_weights)
308
+
309
+ coef_2 = torch.clamp(coef_1, 1 - epsilon_low, 1 + epsilon_high)
310
+
311
+ if delta is not None:
312
+ loss_1 = torch.clamp(coef_1, max=delta) * advantages.unsqueeze(1)
313
+ else:
314
+ loss_1 = coef_1 * advantages.unsqueeze(1)
315
+ pass
316
+
317
+ # Must detach - otherwise gradients are not propagated correctly!
318
+ # exp(x - x) == 1
319
+ # loss_i = torch.exp(new - new.detach()) * advantages.unsqueeze(1)
320
+
321
+ loss_2 = coef_2 * advantages.unsqueeze(1)
322
+ loss_i = -torch.min(loss_1, loss_2)
323
+
324
+ if use_vllm and sampling_per_token_logps is not None:
325
+ loss_i = loss_i * importance_sampling_ratio
326
+ #delta for metric
327
+ with torch.no_grad():
328
+ delta = torch.abs(old - sampling_per_token_logps)
329
+ delta = delta * mask
330
+ flat_is_ratio = importance_sampling_ratio * mask
331
+ else:
332
+ delta = torch.tensor([]).detach()
333
+ flat_is_ratio = torch.tensor([]).detach()
334
+ if beta != 0.0:
335
+ loss_i = loss_i + beta * kl_i
336
+
337
+ mask = mask.to(torch.float32)
338
+ n_mask_per_reward = mask.sum(1)
339
+
340
+ # https://github.com/huggingface/trl/blob/e8b8499f1f8d76838155b515e414ee98f757d6d5/trl/trainer/grpo_trainer.py#L1624
341
+ if loss_type == "grpo":
342
+ loss = ((loss_i * mask).sum(-1) / mask.sum(-1).clamp(min=1.0)).mean()
343
+ loss = loss / current_gradient_accumulation_steps
344
+ elif loss_type == "bnpo":
345
+ loss = (loss_i * mask).sum() / mask.sum().clamp(min=1.0)
346
+ loss = loss / current_gradient_accumulation_steps
347
+ elif loss_type == "dr_grpo":
348
+ loss = (loss_i * mask).sum() / (loss_i.size(0) * max_completion_length)
349
+ loss = loss / current_gradient_accumulation_steps
350
+ elif loss_type == "dapo":
351
+ normalizer = num_items_in_batch/ num_processes
352
+ loss = (loss_i * mask).sum() / normalizer
353
+ else:
354
+ raise ValueError(f"Unknown loss type: {loss_type}")
355
+
356
+ # loss = (loss_i * mask).sum() / mask.sum()
357
+
358
+ # Get metrics as well which are folded
359
+ def masked_batch_mean(x):
360
+ with torch.inference_mode():
361
+ completion_length = n_mask_per_reward.mean()
362
+ if x.shape[1] == 1: # when importance_sampling_level == "sequence"
363
+ return completion_length, x.mean()
364
+ else:
365
+ mean_kl_per_reward = (x * mask).sum(1) / n_mask_per_reward
366
+ mean_kl = mean_kl_per_reward.mean()
367
+ return completion_length, mean_kl
368
+ completion_length, mean_kl = masked_batch_mean(kl_i)
369
+ return loss, completion_length, mean_kl, delta, flat_is_ratio
370
+
371
+ class UnslothEfficientGRPO(torch.autograd.Function):
372
+ # All Unsloth Zoo code licensed under LGPLv3
373
+ @staticmethod
374
+ def forward(ctx, _new_hidden_states, _old_hidden_states, _ref_hidden_states, _sampling_per_token_logps, lm_head, _input_ids, _mask, _advantages, beta, scaler = None, n_chunks = 1, extra_kwargs=None):
375
+ if extra_kwargs is None:
376
+ extra_kwargs = {}
377
+ def compute_loss(new_hidden_states, old_hidden_states, ref_hidden_states, sampling_per_token_logps, input_ids, mask, advantages, scaling):
378
+ new_logits = torch.matmul(new_hidden_states.to(lm_head.dtype), lm_head.t())
379
+ new_logits = new_logits[:, :-1, :] # exclude the last logit: it corresponds to the next token pred
380
+ with torch.no_grad():
381
+ if beta != 0.0:
382
+ ref_logits = torch.matmul(ref_hidden_states.to(lm_head.dtype), lm_head.t())
383
+ ref_logits = ref_logits[:, :-1, :] # exclude the last logit: it corresponds to the next token pred
384
+ else:
385
+ ref_logits = None
386
+ if old_hidden_states is not None:
387
+ old_logits = torch.matmul(old_hidden_states.to(lm_head.dtype), lm_head.t())
388
+ old_logits = old_logits[:, :-1, :] # exclude the last logit: it corresponds to the next token pred
389
+ else:
390
+ old_logits = None
391
+ # if old_hidden_states is not None:
392
+ # old_logits = torch.matmul(old_hidden_states, lm_head.t()) #last logit already excluded
393
+ # old_logits = old_logits[:, :-1, :] # exclude the last logit: it corresponds to the next token pred
394
+ # else:
395
+ # old_logits = None
396
+ # unsloth_zoo/rl_replacements.py
397
+ loss, completion_length, mean_kl, delta, flat_is_ratio = grpo_compute_loss(
398
+ ref_logits,
399
+ new_logits,
400
+ old_logits,
401
+ sampling_per_token_logps,
402
+ input_ids,
403
+ mask,
404
+ beta,
405
+ advantages,
406
+ **extra_kwargs,
407
+ )
408
+
409
+ # Scale loss if needed for mixed precision training
410
+ scaled_loss = loss * scaling
411
+ # Must add .loss.detach otherwise autograd uses 2x VRAM
412
+ return scaled_loss, (loss.detach(), completion_length, mean_kl, delta, flat_is_ratio)
413
+ pass
414
+
415
+ device =_new_hidden_states.device
416
+ grad_inputs = torch.empty_like(_new_hidden_states)
417
+ accumulated_loss = torch.zeros(1, device = device)
418
+ accumulated_completion_length = torch.zeros(1, device = device)
419
+ accumulated_mean_kl = torch.zeros(1, device = device)
420
+ accumulated_delta = []
421
+ accumulated_flat_is_ratio = []
422
+ def accumulate_chunk(
423
+ new_hidden_states_j,
424
+ old_hidden_states_j,
425
+ ref_hidden_states_j,
426
+ sampling_per_token_logps_j,
427
+ input_ids_j,
428
+ mask_j,
429
+ advantages_j,
430
+ scaling,
431
+ grad_inputs_j,
432
+ ):
433
+ (chunk_grad_input,), (chunk_loss, (unscaled_loss, chunk_completion_length, chunk_mean_kl, chunk_delta, chunk_flat_is_ratio)) = torch.func.grad_and_value(
434
+ compute_loss,
435
+ argnums = (0,),
436
+ has_aux = True,
437
+ )(new_hidden_states_j, old_hidden_states_j, ref_hidden_states_j, sampling_per_token_logps_j, input_ids_j, mask_j, advantages_j, scaling)
438
+ accumulated_loss .add_(unscaled_loss)
439
+ accumulated_completion_length.add_(chunk_completion_length)
440
+ accumulated_mean_kl .add_(chunk_mean_kl)
441
+ accumulated_delta .append(chunk_delta)
442
+ accumulated_flat_is_ratio .append(chunk_flat_is_ratio)
443
+ grad_inputs_j[:] = chunk_grad_input
444
+ pass
445
+
446
+ accumulate_chunk = torch.compile(
447
+ accumulate_chunk,
448
+ fullgraph = True,
449
+ # [TODO] Dynamic marking causes torch.compile errors if sequence length is long
450
+ dynamic = True,
451
+ options = torch_compile_options,
452
+ )
453
+
454
+ grad_inputs_chunks = torch.chunk(grad_inputs, chunks = n_chunks, dim = 0)
455
+ new_hidden_states = torch.chunk(_new_hidden_states, chunks = n_chunks, dim = 0)
456
+ if _old_hidden_states is not None:
457
+ old_hidden_states = torch.chunk(_old_hidden_states, chunks = n_chunks, dim = 0)
458
+ else:
459
+ old_hidden_states = [None] * n_chunks
460
+ if _ref_hidden_states is not None:
461
+ ref_hidden_states = torch.chunk(_ref_hidden_states, chunks = n_chunks, dim = 0)
462
+ else:
463
+ ref_hidden_states = [None] * n_chunks
464
+ if _sampling_per_token_logps is not None:
465
+ sampling_per_token_logps = torch.chunk(_sampling_per_token_logps, chunks = n_chunks, dim = 0)
466
+ else:
467
+ sampling_per_token_logps = [None] * n_chunks
468
+ input_ids = torch.chunk(_input_ids, chunks = n_chunks, dim = 0)
469
+ mask = torch.chunk(_mask, chunks = n_chunks, dim = 0)
470
+ advantages = torch.chunk(_advantages, chunks = n_chunks, dim = 0)
471
+
472
+ # Get mixed precision scaling if seen
473
+ scaling = scaler.get_scale() if scaler is not None else 1.0
474
+
475
+ # Force torch.compile to use dynamic shapes for seqlen dim
476
+ # mark_dynamic = lambda x: torch._dynamo.mark_dynamic(x, 1)
477
+
478
+ for (grad_inputs_j, new_hidden_states_j, old_hidden_states_j, ref_hidden_states_j, sampling_per_token_logps_j, input_ids_j, mask_j, advantages_j, ) in \
479
+ zip(grad_inputs_chunks, new_hidden_states, old_hidden_states, ref_hidden_states, sampling_per_token_logps, input_ids, mask, advantages):
480
+
481
+ # [TODO] Dynamic marking causes torch.compile errors if sequence length is long
482
+
483
+ # mark_dynamic(new_hidden_states_j)
484
+ # mark_dynamic(ref_hidden_states_j)
485
+ # if old_hidden_states_j is not None:
486
+ # mark_dynamic(old_hidden_states_j)
487
+ # mark_dynamic(input_ids_j)
488
+ # mark_dynamic(mask_j)
489
+
490
+ accumulate_chunk(
491
+ new_hidden_states_j,
492
+ old_hidden_states_j,
493
+ ref_hidden_states_j,
494
+ sampling_per_token_logps_j,
495
+ input_ids_j,
496
+ mask_j,
497
+ advantages_j,
498
+ scaling,
499
+ grad_inputs_j,
500
+ )
501
+ pass
502
+
503
+ grad_inputs .div_(n_chunks)
504
+ accumulated_loss .div_(n_chunks)
505
+ accumulated_completion_length.div_(n_chunks)
506
+ accumulated_mean_kl .div_(n_chunks)
507
+
508
+ if _sampling_per_token_logps is not None:
509
+ accumulated_delta = torch.cat(accumulated_delta, dim=0)
510
+ accumulated_flat_is_ratio = torch.cat(accumulated_flat_is_ratio, dim=0)
511
+ else:
512
+ accumulated_delta = None
513
+ accumulated_flat_is_ratio = None
514
+ ctx.save_for_backward(grad_inputs)
515
+ return (
516
+ accumulated_loss,
517
+ accumulated_completion_length,
518
+ accumulated_mean_kl,
519
+ accumulated_delta,
520
+ accumulated_flat_is_ratio
521
+ )
522
+ pass
523
+
524
+ @staticmethod
525
+ def backward(ctx, grad_output, dcompletion_length, dmean_kl, ddelta, ddflat_is_ratio):
526
+ (grad_input,) = ctx.saved_tensors
527
+ return (grad_input, None, None, None, None, None, None, None, None, None, None, None)
528
+ pass
529
+
530
+ def grpo_accumulated_loss(
531
+ trainer,
532
+ input_ids,
533
+ attention_mask,
534
+ logits_to_keep,
535
+ completion_mask,
536
+ advantages,
537
+ old_hidden_states,
538
+ ref_hidden_states,
539
+ n_chunks = -1,
540
+ **kwargs,
541
+ ):
542
+ # All Unsloth Zoo code licensed under LGPLv3
543
+ bsz, qlen = input_ids.shape
544
+
545
+ pixel_values = kwargs.get('pixel_values',None)
546
+ image_grid_thw = kwargs.get('image_grid_thw',None)
547
+ pixel_attention_mask = kwargs.get('pixel_attention_mask',None)
548
+ image_sizes = kwargs.get('image_sizes',None)
549
+ #delete this from kwargs so less issues
550
+ sampling_per_token_logps = kwargs.pop("sampling_per_token_logps", None)
551
+ kwargs["vllm_importance_sampling_cap"] = trainer.vllm_importance_sampling_cap if sampling_per_token_logps is not None else None
552
+ kwargs["use_vllm"] = trainer.use_vllm
553
+ # Find closest multiple
554
+ factors = [i for i in range(1, bsz + 1) if bsz % i == 0]
555
+ if n_chunks == -1: n_chunks = bsz
556
+ n_chunks = factors[min(np.searchsorted(factors, n_chunks), len(factors)-1)]
557
+
558
+ if not hasattr(trainer, '_autocast_dtype'):
559
+ trainer._autocast_dtype = torch.float16 if os.environ.get('ACCELERATE_MIXED_PRECISION', 'fp16') == 'fp16' else torch.bfloat16
560
+ if os.environ.get('UNSLOTH_FORCE_FLOAT32', '0') == '1': trainer._autocast_dtype = None
561
+ pass
562
+ os.environ["UNSLOTH_RETURN_HIDDEN_STATES"] = "1"
563
+
564
+ lm_head = trainer.model.get_output_embeddings().weight
565
+
566
+ if pixel_values is None:
567
+ left_pad_tokens_per_prompt = calculate_pad_tokens_in_prompt(input_ids, logits_to_keep, trainer.processing_class.pad_token_id)
568
+
569
+ max_left_pad = max(left_pad_tokens_per_prompt).item()
570
+
571
+ input_ids = left_pack_padding(input_ids, trainer.processing_class.pad_token_id)
572
+
573
+ completion_input_ids = input_ids[:, -(logits_to_keep +max_left_pad):]
574
+
575
+ completion_mask = create_completion_attention_mask(completion_input_ids, left_pad_tokens_per_prompt, max_left_pad, trainer.processing_class.pad_token_id).to(attention_mask.dtype)
576
+ #TODO given the completion mask here we need to, handle the left pad tokens so the sizes of completion
577
+ #token or old logprobs are compatible with the importance sampling logprobs
578
+ if trainer.use_vllm and sampling_per_token_logps is not None:
579
+ sampling_per_token_logps = align_logprobs_with_mask(sampling_per_token_logps, completion_mask)
580
+ attention_mask = input_ids != trainer.processing_class.pad_token_id
581
+ attention_mask = attention_mask.to(attention_mask.dtype)
582
+ else:
583
+ completion_input_ids = input_ids[:, -logits_to_keep:]
584
+
585
+ unwrapped_model = trainer.accelerator.unwrap_model(trainer.model, keep_fp32_wrapper = False)
586
+
587
+ # Do not move hidden_states from device 1 to device 0:
588
+ for module in unwrapped_model.modules():
589
+ if hasattr(module, "_hf_hook") and hasattr(module._hf_hook, "io_same_decice"):
590
+ module._hf_hook.io_same_decice = False
591
+ pass
592
+ # Get autocaster
593
+ if trainer._autocast_dtype is None:
594
+ autocaster = nullcontext()
595
+ else:
596
+ autocaster = torch.amp.autocast(device_type = trainer.model.device.type, dtype = trainer._autocast_dtype)
597
+ with autocaster:
598
+ if pixel_values is None:
599
+ new_hidden_states = unwrapped_model(
600
+ input_ids = input_ids,
601
+ attention_mask = attention_mask,
602
+ pixel_values = pixel_values,
603
+ image_grid_thw = image_grid_thw,
604
+ pixel_attention_mask = pixel_attention_mask,
605
+ image_sizes = image_sizes,
606
+ # logits_to_keep = logits_to_keep + 1,
607
+ ).logits
608
+
609
+ #keep extra logit as we generated a new token
610
+ new_hidden_states = new_hidden_states[:, -(logits_to_keep +max_left_pad+1): , :]
611
+ if ref_hidden_states is not None:
612
+ ref_hidden_states = ref_hidden_states[:, -(logits_to_keep +max_left_pad+1): , :]
613
+ if old_hidden_states is not None:
614
+ old_hidden_states = old_hidden_states[:, -(logits_to_keep +max_left_pad+1): , :]
615
+ else:
616
+ new_hidden_states = unwrapped_model(
617
+ input_ids = input_ids,
618
+ attention_mask = attention_mask,
619
+ pixel_values = pixel_values,
620
+ image_grid_thw = image_grid_thw,
621
+ pixel_attention_mask = pixel_attention_mask,
622
+ image_sizes = image_sizes,
623
+ logits_to_keep = logits_to_keep + 1,
624
+ ).logits
625
+ loss, completion_length, mean_kl, delta, flat_is_ratio = UnslothEfficientGRPO.apply(
626
+ new_hidden_states,
627
+ old_hidden_states,
628
+ ref_hidden_states,
629
+ sampling_per_token_logps,
630
+ lm_head,
631
+ completion_input_ids,
632
+ completion_mask,
633
+ advantages,
634
+ trainer.beta,
635
+ trainer.accelerator.scaler,
636
+ n_chunks,
637
+ kwargs # pass kwargs as a dict
638
+ )
639
+ # Must force not returning hidden states but logits otherwise gibberish
640
+ os.environ["UNSLOTH_RETURN_HIDDEN_STATES"] = "0"
641
+
642
+ return loss, completion_length, mean_kl, delta, flat_is_ratio
643
+ # Old non efficient code path
644
+ new_logits = torch.matmul(new_hidden_states, lm_head.t())
645
+ new_logits = new_logits[:, :-1, :] # exclude the last logit: it corresponds to the next token pred
646
+ old_logits = torch.matmul(old_hidden_states, lm_head.t())
647
+ old_logits = old_logits[:, :-1, :] # exclude the last logit: it corresponds to the next token pred
648
+ loss, completion_length, mean_kl = grpo_compute_loss(
649
+ old_logits,
650
+ new_logits,
651
+ completion_input_ids,
652
+ completion_mask,
653
+ trainer.beta,
654
+ advantages,
655
+ )
656
+ return loss, completion_length, mean_kl
657
+ pass
658
+
659
+ @torch.compile(dynamic = True, fullgraph = True, options = torch_compile_options)
660
+ def grpo_compute_loss_slow(
661
+ ref_logits,
662
+ new_logits,
663
+ old_logits,
664
+ sampling_per_token_logps,
665
+ input_ids,
666
+ mask,
667
+ beta,
668
+ advantages,
669
+ **kwargs
670
+ ):
671
+ # All Unsloth Zoo code licensed under LGPLv3
672
+ # Set defaults for optional arguments
673
+ loss_type = kwargs.get("loss_type", "grpo")
674
+ epsilon_low = kwargs.get("epsilon_low", 0.2)
675
+ epsilon_high = kwargs.get("epsilon_high", 0.2)
676
+ max_completion_length = kwargs.get("max_completion_length", 8192)
677
+ delta = kwargs.get("delta", None)
678
+ temperature = kwargs.get("temperature", 1.0)
679
+ logit_scale_multiply = kwargs.get("logit_scale_multiply", 0.0)
680
+ logit_scale_divide = kwargs.get("logit_scale_divide", 0.0)
681
+ logit_softcapping = kwargs.get("logit_softcapping", 0.0)
682
+ importance_sampling_level = kwargs.get("importance_sampling_level", "token")
683
+ num_items_in_batch = kwargs.get("num_items_in_batch", None)
684
+ current_gradient_accumulation_steps = kwargs.get("current_gradient_accumulation_steps", 1)
685
+ num_processes = kwargs.get("num_processes", 1)
686
+ use_vllm = kwargs.get("use_vllm", False)
687
+ vllm_importance_sampling_cap = kwargs.get("vllm_importance_sampling_cap", 2.0)
688
+ input_ids = input_ids.unsqueeze(-1)
689
+
690
+ # Optional logit softcapping and logit dividing
691
+ if logit_scale_multiply != 0: new_logits = new_logits * logit_scale_multiply
692
+ if logit_scale_divide != 0: new_logits = new_logits / logit_scale_divide
693
+ if logit_softcapping != 0: new_logits = new_logits * torch.tanh(new_logits / logit_softcapping)
694
+
695
+ new_logits = new_logits.to(torch.float32)
696
+ # See https://huggingface.co/blog/the_n_implementation_details_of_rlhf_with_ppo#policy-training-implementation-details
697
+ if temperature != 1.0: new_logits = new_logits / temperature
698
+ new_x = torch.gather(new_logits, dim = -1, index = input_ids).squeeze(-1)
699
+ new = new_x - torch.logsumexp(new_logits, dim = -1)
700
+ # x_i - logsumexp(x_i)
701
+ with torch.no_grad():
702
+ if beta != 0.0:
703
+ assert ref_logits is not None, "ref_logits should not be None when beta != 0.0"
704
+
705
+ # Optional logit softcapping and logit dividing
706
+ if logit_scale_multiply != 0: ref_logits = ref_logits * logit_scale_multiply
707
+ if logit_scale_divide != 0: ref_logits = ref_logits / logit_scale_divide
708
+ if logit_softcapping != 0: ref_logits = ref_logits * torch.tanh(ref_logits / logit_softcapping)
709
+
710
+ ref_logits = ref_logits.to(torch.float32)
711
+ # See https://huggingface.co/blog/the_n_implementation_details_of_rlhf_with_ppo#policy-training-implementation-details
712
+ if temperature != 1.0: ref_logits = ref_logits / temperature
713
+ ref_x = torch.gather(ref_logits, dim = -1, index = input_ids).squeeze(-1)
714
+ ref = ref_x - torch.logsumexp(ref_logits, dim = -1)
715
+ pass
716
+
717
+ if old_logits is not None:
718
+ # Optional logit softcapping and logit dividing
719
+ if logit_scale_multiply != 0: old_logits = old_logits * logit_scale_multiply
720
+ if logit_scale_divide != 0: old_logits = old_logits / logit_scale_divide
721
+ if logit_softcapping != 0: old_logits = old_logits * torch.tanh(old_logits / logit_softcapping)
722
+
723
+ old_logits = old_logits.to(torch.float32)
724
+ # See https://huggingface.co/blog/the_n_implementation_details_of_rlhf_with_ppo#policy-training-implementation-details
725
+ if temperature != 1.0: old_logits = old_logits / temperature
726
+ old_x = torch.gather(old_logits, dim = -1, index = input_ids).squeeze(-1)
727
+ old = old_x - torch.logsumexp(old_logits, dim = -1)
728
+ pass
729
+ if use_vllm and sampling_per_token_logps is not None:
730
+ #must filter out extra prompt tokens in begining after making input_ids left padded
731
+ importance_sampling_ratio = torch.exp((old * mask) - sampling_per_token_logps)
732
+ importance_sampling_ratio = torch.clamp(
733
+ importance_sampling_ratio, max=vllm_importance_sampling_cap
734
+ )
735
+ pass
736
+ pass
737
+
738
+ # Reverse KL
739
+ # Note that this is a low variance low bias estimator for the KL divergence as used in GRPO paper
740
+ if beta != 0.0:
741
+ kl_i = torch.exp(ref - new) - (ref - new) - 1.0
742
+
743
+ else:
744
+ # set kl_i to a tensor of zeros with the correct shape
745
+ if importance_sampling_level == "sequence":
746
+ kl_i = new.new_zeros(new.size(0), 1)
747
+ else:
748
+ kl_i = torch.zeros_like(new)
749
+ # Full correct reverse KL divergence?? Missing term maybe?
750
+ # kl_i = torch.exp(new) * kl_i
751
+
752
+ # Below is forward KL (normal KL)
753
+ # kl_i = torch.exp(old) * (old - new)
754
+ if old_logits is not None:
755
+ log_ratio = new - old
756
+ else:
757
+ log_ratio = new - new.detach()
758
+
759
+ if importance_sampling_level == "token":
760
+ log_importance_weights = log_ratio
761
+ elif importance_sampling_level == "sequence":
762
+ log_importance_weights = (log_ratio * mask).sum(-1) / mask.sum(-1).clamp(min=1.0)
763
+ log_importance_weights = log_importance_weights.unsqueeze(-1)
764
+ else:
765
+ raise ValueError(
766
+ f"Unknown importance sampling level: {importance_sampling_level}. Possible values are 'token' "
767
+ "and 'sequence'."
768
+ )
769
+
770
+ coef_1 = torch.exp(log_importance_weights)
771
+
772
+ coef_2 = torch.clamp(coef_1, 1 - epsilon_low, 1 + epsilon_high)
773
+
774
+ if delta is not None:
775
+ loss_1 = torch.clamp(coef_1, max=delta) * advantages.unsqueeze(1)
776
+ else:
777
+ loss_1 = coef_1 * advantages.unsqueeze(1)
778
+ pass
779
+
780
+ # Must detach - otherwise gradients are not propagated correctly!
781
+ # exp(x - x) == 1
782
+ # loss_i = torch.exp(new - new.detach()) * advantages.unsqueeze(1)
783
+
784
+ loss_2 = coef_2 * advantages.unsqueeze(1)
785
+ loss_i = -torch.min(loss_1, loss_2)
786
+
787
+ if use_vllm and sampling_per_token_logps is not None:
788
+ loss_i = loss_i * importance_sampling_ratio
789
+ #delta for metric
790
+ with torch.no_grad():
791
+ delta = torch.abs(old - sampling_per_token_logps)
792
+ delta = delta * mask
793
+ flat_is_ratio = importance_sampling_ratio * mask
794
+ else:
795
+ delta = torch.tensor([]).detach()
796
+ flat_is_ratio = torch.tensor([]).detach()
797
+ if beta != 0.0:
798
+ loss_i = loss_i + beta * kl_i
799
+
800
+ mask = mask.to(torch.float32)
801
+ n_mask_per_reward = mask.sum(1)
802
+
803
+ # https://github.com/huggingface/trl/blob/e8b8499f1f8d76838155b515e414ee98f757d6d5/trl/trainer/grpo_trainer.py#L1624
804
+ if loss_type == "grpo":
805
+ loss = ((loss_i * mask).sum(-1) / mask.sum(-1).clamp(min=1.0)).mean()
806
+ loss = loss / current_gradient_accumulation_steps
807
+ elif loss_type == "bnpo":
808
+ loss = (loss_i * mask).sum() / mask.sum().clamp(min=1.0)
809
+ loss = loss / current_gradient_accumulation_steps
810
+ elif loss_type == "dr_grpo":
811
+ loss = (loss_i * mask).sum() / (loss_i.size(0) * max_completion_length)
812
+ loss = loss / current_gradient_accumulation_steps
813
+ elif loss_type == "dapo":
814
+ normalizer = num_items_in_batch/ num_processes
815
+ loss = (loss_i * mask).sum() / normalizer
816
+ else:
817
+ raise ValueError(f"Unknown loss type: {loss_type}")
818
+
819
+ # loss = (loss_i * mask).sum() / mask.sum()
820
+
821
+ # Get metrics as well which are folded
822
+ def masked_batch_mean(x):
823
+ with torch.inference_mode():
824
+ completion_length = n_mask_per_reward.mean()
825
+ if x.shape[1] == 1: # when importance_sampling_level == "sequence"
826
+ return completion_length, x.mean()
827
+ else:
828
+ mean_kl_per_reward = (x * mask).sum(1) / n_mask_per_reward
829
+ mean_kl = mean_kl_per_reward.mean()
830
+ return completion_length, mean_kl
831
+ completion_length, mean_kl = masked_batch_mean(kl_i)
832
+ return loss, completion_length, mean_kl, delta, flat_is_ratio
833
+
834
+ def grpo_update_SamplingParams(SamplingParams, generation_kwargs, vllm_sampling_params = None):
835
+ good_sampling_params_keys = inspect.signature(SamplingParams).parameters.keys()
836
+
837
+ # Filter generation_kwargs
838
+ new_generation_kwargs = {}
839
+ for key in generation_kwargs.keys():
840
+ if key in good_sampling_params_keys:
841
+ new_generation_kwargs[key] = generation_kwargs[key]
842
+ generation_kwargs = new_generation_kwargs
843
+
844
+ if vllm_sampling_params is not None:
845
+ for key in good_sampling_params_keys:
846
+ if hasattr(vllm_sampling_params, key):
847
+ overwrited_key = getattr(vllm_sampling_params, key)
848
+ if overwrited_key is not None and (type(overwrited_key) in (list, tuple,) and len(overwrited_key) != 0):
849
+ generation_kwargs[key] = overwrited_key
850
+ return generation_kwargs
851
+
852
+ def _get_inference_mode_context_manager(model: torch.nn.Module):
853
+ """
854
+ If the state dict was quantized using torchao, we will run into
855
+ the following error when calling ops like aten.t() in inference mode.
856
+ This is a bug in PyTorch that affects all tensor subclasses.
857
+
858
+ Cannot set version_counter for inference tensor
859
+
860
+ For now, we work around this issue by using `torch.no_grad()` in this case.
861
+ See https://github.com/pytorch/pytorch/issues/164872 for more details.
862
+ Otherwise, just return `torch.inference_mode()`.
863
+ """
864
+ torchao_config = getattr(model, "torchao_config", None)
865
+ if torchao_config is not None and torchao_config.qat_scheme is None:
866
+ return torch.no_grad()
867
+ else:
868
+ return torch.inference_mode()
869
+
870
+ def vLLMSamplingParams(**kwargs):
871
+ from vllm import SamplingParams
872
+
873
+ sampling_params = SamplingParams(**kwargs)
874
+ sampling_params._set_kwargs = kwargs
875
+ return sampling_params
876
+ @dataclass
877
+ class UnslothGRPOConfig(GRPOConfig):
878
+ """
879
+
880
+ Configuration class for the [`GRPOTrainer`].
881
+
882
+ This class includes only the parameters that are specific to GRPO training. For a full list of training arguments,
883
+ please refer to the [`~transformers.TrainingArguments`] documentation. Note that default values in this class may
884
+ differ from those in [`~transformers.TrainingArguments`].
885
+
886
+ Using [`~transformers.HfArgumentParser`] we can turn this class into
887
+ [argparse](https://docs.python.org/3/library/argparse#module-argparse) arguments that can be specified on the
888
+ command line.
889
+
890
+ Parameters:
891
+ > Parameters that control the model and reference model
892
+
893
+ model_init_kwargs (`str`, `dict[str, Any]`, *optional*):
894
+ Keyword arguments for [`~transformers.AutoModelForCausalLM.from_pretrained`], used when the `model`
895
+ argument of the [`GRPOTrainer`] is provided as a string.
896
+ disable_dropout (`bool`, *optional*, defaults to `False`):
897
+ Whether to disable dropout in the model. This is useful for training with a reference model, as it prevents
898
+ the model from generating different logprobs for the same input.
899
+
900
+ > Parameters that control the data preprocessing
901
+
902
+ remove_unused_columns (`bool`, *optional*, defaults to `False`):
903
+ Whether to only keep the column `"prompt"` in the dataset. If you use a custom reward function that
904
+ requires any column other than `"prompts"` and `"completions"`, you should keep this to `False`.
905
+ max_prompt_length (`int` or `None`, *optional*, defaults to `512`):
906
+ Maximum length of the prompt. If the prompt is longer than this value, it will be truncated left.
907
+ num_generations (`int` or `None`, *optional*, defaults to `8`):
908
+ Number of generations per prompt to sample. The effective batch size (num_processes * per_device_batch_size
909
+ * gradient_accumulation_steps) must be evenly divisible by this value.
910
+ max_completion_length (`int` or `None`, *optional*, defaults to `256`):
911
+ Maximum length of the generated completion.
912
+ ds3_gather_for_generation (`bool`, *optional*, defaults to `True`):
913
+ This setting applies to DeepSpeed ZeRO-3. If enabled, the policy model weights are gathered for generation,
914
+ improving generation speed. However, disabling this option allows training models that exceed the VRAM
915
+ capacity of a single GPU, albeit at the cost of slower generation. Disabling this option is not compatible
916
+ with vLLM generation.
917
+ shuffle_dataset (`bool`, *optional*, defaults to `True`):
918
+ Whether to shuffle the training dataset.
919
+
920
+ > Parameters that control generation
921
+
922
+ generation_batch_size: (`int`, *optional*):
923
+ Batch size to use for generation. If `None`, it defaults to the effective training batch size:
924
+ `per_device_train_batch_size * num_processes * steps_per_generation`. In other words, there is one
925
+ generation batch processed per optimization step. Mutually exclusive with `steps_per_generation`.
926
+ steps_per_generation: (`int`, *optional*):
927
+ Number of steps per generation. If `None`, it defaults to `gradient_accumulation_steps`. Mutually exclusive
928
+ with `generation_batch_size`.
929
+ temperature (`float`, defaults to `1.0`):
930
+ Temperature for sampling. The higher the temperature, the more random the completions.
931
+ top_p (`float`, *optional*, defaults to `1.0`):
932
+ Float that controls the cumulative probability of the top tokens to consider. Must be in (0, 1]. Set to
933
+ `1.0` to consider all tokens.
934
+ top_k (`int`, *optional*):
935
+ Number of highest probability vocabulary tokens to keep for top-k-filtering. If `None`, top-k-filtering is
936
+ disabled and all tokens are considered.
937
+ min_p (`float`, *optional*):
938
+ Minimum token probability, which will be scaled by the probability of the most likely token. It must be a
939
+ value between `0.0` and `1.0`. Typical values are in the `0.01-0.2` range.
940
+ repetition_penalty (`float`, *optional*, defaults to `1.0`):
941
+ Float that penalizes new tokens based on whether they appear in the prompt and the generated text so far.
942
+ Values > `1.0` encourage the model to use new tokens, while values < `1.0` encourage the model to repeat
943
+ tokens.
944
+ use_transformers_paged (`bool`, *optional*, defaults to `False`):
945
+ Whether to use the `transformers` paged implementation for generation. If set to `True`, the `transformers`
946
+ paged implementation will be used for generation instead of the default padded implementation. This
947
+ parameter is only effective when `use_vllm` is set to `False`.
948
+ cache_implementation (`str`, *optional*):
949
+ Implementation of the cache method for faster generation when `use_vllm` is set to `False`.
950
+ generation_kwargs (`dict[str, Any]`, *optional*):
951
+ Additional keyword arguments to pass to [`~transformers.GenerationConfig`] (if using transformers) or
952
+ `SamplingParams` (if using vLLM) when sampling completions. This can be used to further customize the
953
+ generation behavior, such as setting `suppress_tokens`, `num_beams`, etc. If it contains keys that conflict
954
+ with the other generation parameters (like `min_p`, `top_p`, etc.), they will override them.
955
+
956
+ > Parameters that control generation acceleration powered by vLLM
957
+
958
+ use_vllm (`bool`, *optional*, defaults to `False`):
959
+ Whether to use vLLM for generating completions. If set to `True`, the trainer will use vLLM for generation
960
+ instead of the default model.generate(). Requires `vllm` to be installed.
961
+ vllm_mode (`str`, *optional*, defaults to `"server"`):
962
+ Mode to use for vLLM integration when `use_vllm` is set to `True`. Must be one of `"server"` or
963
+ `"colocate"`.
964
+
965
+ - `"server"`: The trainer will send generation requests to a separate vLLM server. Make sure a TRL vLLM
966
+ server is running (start with `trl vllm-serve`).
967
+ - `"colocate"`: vLLM will run in the same process and share the training GPUs. This avoids the need for a
968
+ separate server but may cause resource contention with training.
969
+ vllm_model_impl (`str`, *optional*, defaults to `"vllm"`):
970
+ Model implementation to use for vLLM. Must be one of `"transformers"` or `"vllm"`. `"transformers"`: Use
971
+ the `transformers` backend for model implementation. `"vllm"`: Use the `vllm` library for model
972
+ implementation.
973
+ vllm_guided_decoding_regex (`str`, *optional*):
974
+ Regex for vLLM guided decoding. If `None` (default), guided decoding is disabled.
975
+
976
+ > Parameters that control the vLLM server (only used when `vllm_mode` is `"server"`)
977
+
978
+ vllm_server_base_url (`str`, *optional*):
979
+ Base URL for the vLLM server (e.g., `"http://localhost:8000"`). If provided, `vllm_server_host` and
980
+ `vllm_server_port` are ignored.
981
+ vllm_server_host (`str`, *optional*, defaults to `"0.0.0.0"`):
982
+ Host of the vLLM server to connect to. Ignored if `vllm_server_base_url` is provided.
983
+ vllm_server_port (`int`, *optional*, defaults to `8000`):
984
+ Port of the vLLM server to connect to. Ignored if `vllm_server_base_url` is provided.
985
+ vllm_server_timeout (`float`, *optional*, defaults to `240.0`):
986
+ Total timeout duration in seconds to wait for the vLLM server to be up. If the server is not up after the
987
+ timeout, a `ConnectionError` is raised.
988
+
989
+ > Parameters that control colocated vLLM execution (only used when `vllm_mode` is `"colocate"`)
990
+
991
+ vllm_gpu_memory_utilization (`float`, *optional*, defaults to `0.3`):
992
+ Control the GPU memory utilization for vLLM. This setting only applies when `vllm_mode` is set to
993
+ `"colocate"`. If you are using `vllm_mode="server"`, this parameter must be passed separately when
994
+ launching the vLLM server via the `--vllm_gpu_memory_utilization` flag.
995
+ vllm_tensor_parallel_size (`int`, *optional*, defaults to `1`):
996
+ Control the tensor parallel size for vLLM. This setting only applies when `vllm_mode` is set to
997
+ `"colocate"`. If you are using `vllm_mode="server"`, this parameter must be passed separately when
998
+ launching the vLLM server via the `--vllm_tensor_parallel_size` flag.
999
+ vllm_enable_sleep_mode (`bool`, *optional*, defaults to `False`):
1000
+ Whether to enable sleep mode for vLLM. If `True`, vLLM will sleep during the optimization step and woken
1001
+ for weight sync and generation.
1002
+
1003
+ > Parameters that control the training
1004
+
1005
+ beta (`float`, *optional*, defaults to `0.0`):
1006
+ KL coefficient. If `0.0` (default), the reference model is not loaded, reducing memory usage and improving
1007
+ training speed.
1008
+ num_iterations (`int`, *optional*, defaults to `1`):
1009
+ Number of iterations per batch (denoted as μ in the algorithm).
1010
+ epsilon (`float`, *optional*, defaults to `0.2`):
1011
+ Epsilon value for clipping.
1012
+ delta (`float`, *optional*):
1013
+ Enables the upper clipping bound in two-sided GRPO loss when set to a float. If `None` (default), standard
1014
+ GRPO clipping is used. Recommended to be greater than `1 + ε` when enabled. This method is introduced in
1015
+ the [INTELLECT-2 tech report](https://huggingface.co/papers/2505.07291).
1016
+ epsilon_high (`float`, *optional*):
1017
+ Upper-bound epsilon value for clipping. If not specified, it defaults to the same value as the lower-bound
1018
+ specified in argument `epsilon`. Paper [DAPO](https://huggingface.co/papers/2503.14476) recommends `0.28`.
1019
+ importance_sampling_level (`str`, *optional*, defaults to `"token"`):
1020
+ Controls whether importance sampling ratios are computed at the `"token"` or `"sequence"` level. `"token"`
1021
+ keeps the raw per-token log-probability ratios (one weight per token). `"sequence"` averages the
1022
+ log-probability ratios across valid tokens to produce a single ratio per sequence. The [GSPO
1023
+ paper](https://huggingface.co/papers/2507.18071) shows that sequence-level sampling often yields more
1024
+ stable training and better alignment with sequence-level rewards.
1025
+ reward_weights (`list[float]`, *optional*):
1026
+ Weights for each reward function. Must match the number of reward functions. If `None`, all rewards are
1027
+ weighted equally with weight `1.0`.
1028
+ scale_rewards (`str` or `bool`, *optional*, defaults to `"group"`):
1029
+ Specifies the scaling strategy for rewards. Supported values are:
1030
+
1031
+ - `True` or `"group"` (default): rewards are scaled by the standard deviation within each group, ensuring
1032
+ unit variance within a group.
1033
+ - `"batch"`: rewards are scaled by the standard deviation across the entire batch, as recommended in the
1034
+ [PPO Lite paper](https://huggingface.co/papers/2508.08221).
1035
+ - `False` or `"none"`: no scaling is applied. The [Dr. GRPO
1036
+ paper](https://huggingface.co/papers/2503.20783) recommends not scaling rewards, as scaling by the
1037
+ standard deviation introduces a question-level difficulty bias.
1038
+ loss_type (`str`, *optional*, defaults to `"dapo"`):
1039
+ Specifies the loss formulation to use. Supported values are:
1040
+
1041
+ - `"grpo"`: Aggregates token-level losses by normalizing over sequence length. Not recommended due to
1042
+ length bias—this approach tends to prefer shorter completions with positive advantages and longer ones
1043
+ with negative advantages.
1044
+ - `"dr_grpo"`: Aggregates token-level losses by normalizing with a global constant. This method was
1045
+ introduced in the [Dr. GRPO paper](https://huggingface.co/papers/2503.20783) to eliminate length bias.
1046
+ The value of the constant corresponds to `max_completion_length`.
1047
+ - `"dapo"` (default): Aggregates token-level losses by normalizing with the number of active token in the
1048
+ global accumulated batch. This method was introduced in the [DAPO
1049
+ paper](https://huggingface.co/papers/2503.14476) to eliminate length bias.
1050
+ - `"bnpo"`: Aggregates token-level losses by normalizing with the number of active token in the local
1051
+ batch. Note that normalization is performed over the local batch only, so results may slightly vary
1052
+ depending on the local batch size, despite a constant effective batch size. When using
1053
+ `per_device_train_batch_size==1`, the loss is equivalent to the GRPO loss.
1054
+ mask_truncated_completions (`bool`, *optional*, defaults to `False`):
1055
+ When enabled, truncated completions are excluded from the loss calculation, preventing them from being
1056
+ incorrectly penalized and introducing noise during training. According to the
1057
+ [DAPO](https://huggingface.co/papers/2503.14476) paper, this is a good practice for training stability.
1058
+ sync_ref_model (`bool`, *optional*, defaults to `False`):
1059
+ Whether to synchronize the reference model with the active model every `ref_model_sync_steps` steps, using
1060
+ the `ref_model_mixup_alpha` parameter. This synchronization originates from the
1061
+ [TR-DPO](https://huggingface.co/papers/2404.09656) paper.
1062
+ ref_model_mixup_alpha (`float`, *optional*, defaults to `0.6`):
1063
+ α parameter from the [TR-DPO](https://huggingface.co/papers/2404.09656) paper, which controls the mix
1064
+ between the current policy and the previous reference policy during updates. The reference policy is
1065
+ updated according to the equation: `π_ref = α * π_θ + (1 - α) * π_ref_prev`. To use this parameter, you
1066
+ must set `sync_ref_model=True`.
1067
+ ref_model_sync_steps (`int`, *optional*, defaults to `512`):
1068
+ τ parameter from the [TR-DPO](https://huggingface.co/papers/2404.09656) paper, which determines how
1069
+ frequently the current policy is synchronized with the reference policy. To use this parameter, you must
1070
+ set `sync_ref_model=True`.
1071
+ top_entropy_quantile (`float`, *optional*, defaults to `1.0`):
1072
+ ρ parameter from [Beyond the 80/20 Rule](https://huggingface.co/papers/2506.01939). Keeps in the policy
1073
+ loss term only the top-ρ quantile of tokens by entropy of the probability distribution at each sequence
1074
+ position, improving results. Range: `[0.0-1.0]`. A value of `0.0` masks all but the highest entropy token;
1075
+ `1.0` keeps all tokens. The paper recommends a value of `0.2`. If used with
1076
+ `mask_truncated_completions=True`, only tokens from non-truncated completions are considered.
1077
+ use_liger_loss (`bool`, *optional*, defaults to `False`):
1078
+ Whether to use the Liger GRPO loss.
1079
+ vllm_importance_sampling_correction (`bool`, *optional*, defaults to `True`):
1080
+ Whether to apply Truncated Importance Sampling (TIS) between vLLM completion logprobs and recomputed
1081
+ logprobs. [Your Efficient RL Framework Secretly Brings You Off-Policy RL
1082
+ Training](https://fengyao.notion.site/off-policy-rl) highlights that using a separate generation framework
1083
+ (such as vLLM) can introduce off-policy effects due to subtle implementation differences between generation
1084
+ and training backends. TIS is proposed as a remedy for this issue.
1085
+ vllm_importance_sampling_cap (`float`, *optional*, defaults to `2.0`):
1086
+ Truncation parameter C for Truncated Importance Sampling (TIS). This sets an upper bound on the importance
1087
+ sampling ratio, improving training stability.
1088
+
1089
+ > Parameters that control the logging
1090
+
1091
+ log_completions (`bool`, *optional*, defaults to `False`):
1092
+ Whether to log a sample of (prompt, completion) pairs every `logging_steps` steps. If `rich` is installed,
1093
+ it prints the sample. If `wandb` logging is enabled, it logs it to `wandb`.
1094
+ num_completions_to_print (`int`, *optional*):
1095
+ Number of completions to print with `rich`. If `None`, all completions are logged.
1096
+ wandb_log_unique_prompts (`bool`, *optional*, defaults to `False`):
1097
+ Whether to log unique prompts in wandb. If `True`, only unique prompts are logged. If `False`, all prompts
1098
+ are logged.
1099
+
1100
+ """
1101
+ vllm_sampling_params: Optional[Any] = field(
1102
+ default = None,
1103
+ metadata = {'help': 'vLLM SamplingParams'},
1104
+ )
1105
+ unsloth_num_chunks : Optional[int] = field(
1106
+ default = -1,
1107
+ metadata = {'help': 'Chunk size to reduce memory usage. -1 is most efficient.'},
1108
+ )
1109
+
1110
+ def __init__(
1111
+ self,
1112
+ output_dir = None,
1113
+ overwrite_output_dir = None,
1114
+ do_train = False,
1115
+ do_eval = False,
1116
+ do_predict = False,
1117
+ eval_strategy = 'no',
1118
+ prediction_loss_only = False,
1119
+ per_device_train_batch_size = 4,
1120
+ per_device_eval_batch_size = 4,
1121
+ per_gpu_train_batch_size = None,
1122
+ per_gpu_eval_batch_size = None,
1123
+ gradient_accumulation_steps = 2,
1124
+ eval_accumulation_steps = 2,
1125
+ eval_delay = 0,
1126
+ torch_empty_cache_steps = 250,
1127
+ learning_rate = 5e-05,
1128
+ weight_decay = 0.01,
1129
+ adam_beta1 = 0.9,
1130
+ adam_beta2 = 0.999,
1131
+ adam_epsilon = 1e-08,
1132
+ max_grad_norm = 1.0,
1133
+ num_train_epochs = 3.0,
1134
+ max_steps = -1,
1135
+ lr_scheduler_type = 'linear',
1136
+ warmup_ratio = 0.1,
1137
+ warmup_steps = 0,
1138
+ log_level = 'passive',
1139
+ log_level_replica = 'warning',
1140
+ log_on_each_node = True,
1141
+ logging_dir = None,
1142
+ logging_strategy = 'steps',
1143
+ logging_first_step = False,
1144
+ logging_steps = 1,
1145
+ logging_nan_inf_filter = False,
1146
+ save_strategy = 'steps',
1147
+ save_steps = 500,
1148
+ save_total_limit = None,
1149
+ save_safetensors = True,
1150
+ save_on_each_node = False,
1151
+ save_only_model = False,
1152
+ restore_callback_states_from_checkpoint = False,
1153
+ no_cuda = False,
1154
+ use_cpu = False,
1155
+ use_mps_device = False,
1156
+ seed = 3407,
1157
+ data_seed = 3407,
1158
+ jit_mode_eval = False,
1159
+ bf16 = False,
1160
+ fp16 = False,
1161
+ fp16_opt_level = 'O1',
1162
+ half_precision_backend = 'auto',
1163
+ bf16_full_eval = False,
1164
+ fp16_full_eval = False,
1165
+ tf32 = None,
1166
+ local_rank = -1,
1167
+ ddp_backend = None,
1168
+ tpu_num_cores = None,
1169
+ tpu_metrics_debug = False,
1170
+ debug = '',
1171
+ dataloader_drop_last = False,
1172
+ eval_steps = None,
1173
+ dataloader_num_workers = 0,
1174
+ dataloader_prefetch_factor = None,
1175
+ past_index = -1,
1176
+ run_name = None,
1177
+ disable_tqdm = None,
1178
+ remove_unused_columns = False,
1179
+ label_names = None,
1180
+ load_best_model_at_end = False,
1181
+ metric_for_best_model = None,
1182
+ greater_is_better = None,
1183
+ ignore_data_skip = False,
1184
+ fsdp = None,
1185
+ fsdp_min_num_params = 0,
1186
+ fsdp_config = None,
1187
+ fsdp_transformer_layer_cls_to_wrap = None,
1188
+ accelerator_config = None,
1189
+ parallelism_config = None,
1190
+ deepspeed = None,
1191
+ label_smoothing_factor = 0.0,
1192
+ optim = 'adamw_8bit',
1193
+ optim_args = None,
1194
+ adafactor = False,
1195
+ group_by_length = False,
1196
+ length_column_name = 'length',
1197
+ report_to = 'none',
1198
+ project = 'huggingface',
1199
+ trackio_space_id = 'trackio',
1200
+ ddp_find_unused_parameters = None,
1201
+ ddp_bucket_cap_mb = None,
1202
+ ddp_broadcast_buffers = None,
1203
+ dataloader_pin_memory = True,
1204
+ dataloader_persistent_workers = False,
1205
+ skip_memory_metrics = True,
1206
+ use_legacy_prediction_loop = False,
1207
+ push_to_hub = False,
1208
+ resume_from_checkpoint = None,
1209
+ hub_model_id = None,
1210
+ hub_strategy = 'every_save',
1211
+ hub_token = None,
1212
+ hub_private_repo = None,
1213
+ hub_always_push = False,
1214
+ hub_revision = None,
1215
+ gradient_checkpointing = True,
1216
+ gradient_checkpointing_kwargs = None,
1217
+ include_inputs_for_metrics = False,
1218
+ eval_do_concat_batches = True,
1219
+ fp16_backend = 'auto',
1220
+ push_to_hub_model_id = None,
1221
+ push_to_hub_organization = None,
1222
+ push_to_hub_token = None,
1223
+ mp_parameters = '',
1224
+ auto_find_batch_size = False,
1225
+ full_determinism = False,
1226
+ torchdynamo = None,
1227
+ ray_scope = 'last',
1228
+ ddp_timeout = 1800,
1229
+ torch_compile = False,
1230
+ torch_compile_backend = None,
1231
+ torch_compile_mode = None,
1232
+ include_tokens_per_second = False,
1233
+ include_num_input_tokens_seen = False,
1234
+ neftune_noise_alpha = None,
1235
+ optim_target_modules = None,
1236
+ batch_eval_metrics = False,
1237
+ eval_on_start = False,
1238
+ use_liger_kernel = False,
1239
+ liger_kernel_config = None,
1240
+ eval_use_gather_object = False,
1241
+ average_tokens_across_devices = True,
1242
+ model_init_kwargs = None,
1243
+ disable_dropout = False,
1244
+ max_prompt_length = 512,
1245
+ num_generations = 8,
1246
+ max_completion_length = 256,
1247
+ ds3_gather_for_generation = True,
1248
+ shuffle_dataset = True,
1249
+ generation_batch_size = None,
1250
+ steps_per_generation = None,
1251
+ temperature = 1.0,
1252
+ top_p = 1.0,
1253
+ top_k = None,
1254
+ min_p = None,
1255
+ generation_kwargs = {},
1256
+ repetition_penalty = 1.0,
1257
+ use_transformers_paged = False,
1258
+ cache_implementation = None,
1259
+ use_vllm = False,
1260
+ vllm_mode = 'colocate',
1261
+ vllm_model_impl = 'vllm',
1262
+ vllm_enable_sleep_mode = False,
1263
+ vllm_guided_decoding_regex = None,
1264
+ vllm_server_base_url = None,
1265
+ vllm_server_host = '0.0.0.0',
1266
+ vllm_server_port = 8000,
1267
+ vllm_server_timeout = 240.0,
1268
+ vllm_gpu_memory_utilization = 0.3,
1269
+ vllm_tensor_parallel_size = 1,
1270
+ beta = 0.001,
1271
+ num_iterations = 1,
1272
+ epsilon = 0.2,
1273
+ delta = None,
1274
+ epsilon_high = None,
1275
+ importance_sampling_level = 'token',
1276
+ reward_weights = None,
1277
+ scale_rewards = 'group',
1278
+ loss_type = 'bnpo',
1279
+ mask_truncated_completions = False,
1280
+ sync_ref_model = False,
1281
+ ref_model_mixup_alpha = 0.6,
1282
+ ref_model_sync_steps = 512,
1283
+ top_entropy_quantile = 1.0,
1284
+ use_liger_loss = False,
1285
+ vllm_importance_sampling_correction = False,
1286
+ vllm_importance_sampling_cap = 2.0,
1287
+ log_completions = False,
1288
+ num_completions_to_print = None,
1289
+ wandb_log_unique_prompts = False,
1290
+ vllm_sampling_params = None,
1291
+ unsloth_num_chunks = -1,
1292
+
1293
+ **kwargs,
1294
+ ):
1295
+ if learning_rate < 1e-7: print(f'Unsloth: Your learning rate of `{learning_rate}` is too small and less than 1e-7! Consider increasing it, otherwise gradient updates will be close to 0!')
1296
+ if learning_rate > 1: print(f'Unsloth: Your learning rate of `{learning_rate}` is way too larger > 1! Consider decreasing it to 1e-1, otherwise gradient updates will explode!')
1297
+ if output_dir is None and save_strategy == 'steps' and save_steps == 500:
1298
+ output_dir = 'unsloth_training_checkpoints'
1299
+ save_strategy = 'no'
1300
+ if loss_type.lower() == 'dr_grpo':
1301
+ loss_type = 'dr_grpo'
1302
+ elif loss_type.lower() == 'dapo':
1303
+ loss_type = 'dapo'
1304
+ if loss_type.lower() == 'dr_grpo':
1305
+ if scale_rewards == None:
1306
+ scale_rewards = True
1307
+ elif scale_rewards == True:
1308
+ print('Unsloth: The Dr GRPO paper recommends setting `scale_rewards` to False! Will override. Set it to `None` to force False.')
1309
+ scale_rewards = False
1310
+ elif loss_type.lower() == 'dapo':
1311
+ if mask_truncated_completions != True:
1312
+ print('Unsloth: The DAPO paper recommends `mask_truncated_completions = True` - we will set it.')
1313
+ if epsilon_high != 0.28:
1314
+ print('Unsloth: The DAPO paper recommends `epsilon_high = 0.28` - we will set it.')
1315
+ if beta != 0.0:
1316
+ print(f'[WARNING] Unsloth: The DAPO paper recommends setting `beta = 0.0` to remove the KL term - You have set it to {beta}.')
1317
+ mask_truncated_completions = True
1318
+ epsilon_high = 0.28
1319
+
1320
+ if steps_per_generation is None and generation_batch_size is None:
1321
+ ga = gradient_accumulation_steps
1322
+ world_size = int(os.environ.get('WORLD_SIZE', '1'))
1323
+ if (ga * world_size * per_device_train_batch_size) % num_generations != 0:
1324
+ print('Unsloth: We now expect `per_device_train_batch_size` * `gradient_accumulation_steps` * `world_size` to be a multiple of `num_generations`.\nWe will change the batch size of ' + str(per_device_train_batch_size) + ' to the `num_generations` of ' + str(num_generations))
1325
+ per_device_train_batch_size = num_generations
1326
+
1327
+ if temperature <= 0:
1328
+ raise MathError('Unsloth: Please set a positive non-zero temperature since your results will be wrong.')
1329
+ elif temperature >= 10:
1330
+ raise MathError('Unsloth: Please set a positive non-zero temperature less than 10, since sampling will be quite erratic.')
1331
+
1332
+
1333
+ super().__init__(
1334
+ output_dir = output_dir,
1335
+ overwrite_output_dir = overwrite_output_dir,
1336
+ do_train = do_train,
1337
+ do_eval = do_eval,
1338
+ do_predict = do_predict,
1339
+ eval_strategy = eval_strategy,
1340
+ prediction_loss_only = prediction_loss_only,
1341
+ per_device_train_batch_size = per_device_train_batch_size,
1342
+ per_device_eval_batch_size = per_device_eval_batch_size,
1343
+ per_gpu_train_batch_size = per_gpu_train_batch_size,
1344
+ per_gpu_eval_batch_size = per_gpu_eval_batch_size,
1345
+ gradient_accumulation_steps = gradient_accumulation_steps,
1346
+ eval_accumulation_steps = eval_accumulation_steps,
1347
+ eval_delay = eval_delay,
1348
+ torch_empty_cache_steps = torch_empty_cache_steps,
1349
+ learning_rate = learning_rate,
1350
+ weight_decay = weight_decay,
1351
+ adam_beta1 = adam_beta1,
1352
+ adam_beta2 = adam_beta2,
1353
+ adam_epsilon = adam_epsilon,
1354
+ max_grad_norm = max_grad_norm,
1355
+ num_train_epochs = num_train_epochs,
1356
+ max_steps = max_steps,
1357
+ lr_scheduler_type = lr_scheduler_type,
1358
+ warmup_ratio = warmup_ratio,
1359
+ warmup_steps = warmup_steps,
1360
+ log_level = log_level,
1361
+ log_level_replica = log_level_replica,
1362
+ log_on_each_node = log_on_each_node,
1363
+ logging_dir = logging_dir,
1364
+ logging_strategy = logging_strategy,
1365
+ logging_first_step = logging_first_step,
1366
+ logging_steps = logging_steps,
1367
+ logging_nan_inf_filter = logging_nan_inf_filter,
1368
+ save_strategy = save_strategy,
1369
+ save_steps = save_steps,
1370
+ save_total_limit = save_total_limit,
1371
+ save_safetensors = save_safetensors,
1372
+ save_on_each_node = save_on_each_node,
1373
+ save_only_model = save_only_model,
1374
+ restore_callback_states_from_checkpoint = restore_callback_states_from_checkpoint,
1375
+ no_cuda = no_cuda,
1376
+ use_cpu = use_cpu,
1377
+ use_mps_device = use_mps_device,
1378
+ seed = seed,
1379
+ data_seed = data_seed,
1380
+ jit_mode_eval = jit_mode_eval,
1381
+ bf16 = bf16,
1382
+ fp16 = fp16,
1383
+ fp16_opt_level = fp16_opt_level,
1384
+ half_precision_backend = half_precision_backend,
1385
+ bf16_full_eval = bf16_full_eval,
1386
+ fp16_full_eval = fp16_full_eval,
1387
+ tf32 = tf32,
1388
+ local_rank = local_rank,
1389
+ ddp_backend = ddp_backend,
1390
+ tpu_num_cores = tpu_num_cores,
1391
+ tpu_metrics_debug = tpu_metrics_debug,
1392
+ debug = debug,
1393
+ dataloader_drop_last = dataloader_drop_last,
1394
+ eval_steps = eval_steps,
1395
+ dataloader_num_workers = dataloader_num_workers,
1396
+ dataloader_prefetch_factor = dataloader_prefetch_factor,
1397
+ past_index = past_index,
1398
+ run_name = run_name,
1399
+ disable_tqdm = disable_tqdm,
1400
+ remove_unused_columns = remove_unused_columns,
1401
+ label_names = label_names,
1402
+ load_best_model_at_end = load_best_model_at_end,
1403
+ metric_for_best_model = metric_for_best_model,
1404
+ greater_is_better = greater_is_better,
1405
+ ignore_data_skip = ignore_data_skip,
1406
+ fsdp = fsdp,
1407
+ fsdp_min_num_params = fsdp_min_num_params,
1408
+ fsdp_config = fsdp_config,
1409
+ fsdp_transformer_layer_cls_to_wrap = fsdp_transformer_layer_cls_to_wrap,
1410
+ accelerator_config = accelerator_config,
1411
+ parallelism_config = parallelism_config,
1412
+ deepspeed = deepspeed,
1413
+ label_smoothing_factor = label_smoothing_factor,
1414
+ optim = optim,
1415
+ optim_args = optim_args,
1416
+ adafactor = adafactor,
1417
+ group_by_length = group_by_length,
1418
+ length_column_name = length_column_name,
1419
+ report_to = report_to,
1420
+ project = project,
1421
+ trackio_space_id = trackio_space_id,
1422
+ ddp_find_unused_parameters = ddp_find_unused_parameters,
1423
+ ddp_bucket_cap_mb = ddp_bucket_cap_mb,
1424
+ ddp_broadcast_buffers = ddp_broadcast_buffers,
1425
+ dataloader_pin_memory = dataloader_pin_memory,
1426
+ dataloader_persistent_workers = dataloader_persistent_workers,
1427
+ skip_memory_metrics = skip_memory_metrics,
1428
+ use_legacy_prediction_loop = use_legacy_prediction_loop,
1429
+ push_to_hub = push_to_hub,
1430
+ resume_from_checkpoint = resume_from_checkpoint,
1431
+ hub_model_id = hub_model_id,
1432
+ hub_strategy = hub_strategy,
1433
+ hub_token = hub_token,
1434
+ hub_private_repo = hub_private_repo,
1435
+ hub_always_push = hub_always_push,
1436
+ hub_revision = hub_revision,
1437
+ gradient_checkpointing = gradient_checkpointing,
1438
+ gradient_checkpointing_kwargs = gradient_checkpointing_kwargs,
1439
+ include_inputs_for_metrics = include_inputs_for_metrics,
1440
+ eval_do_concat_batches = eval_do_concat_batches,
1441
+ fp16_backend = fp16_backend,
1442
+ push_to_hub_model_id = push_to_hub_model_id,
1443
+ push_to_hub_organization = push_to_hub_organization,
1444
+ push_to_hub_token = push_to_hub_token,
1445
+ mp_parameters = mp_parameters,
1446
+ auto_find_batch_size = auto_find_batch_size,
1447
+ full_determinism = full_determinism,
1448
+ torchdynamo = torchdynamo,
1449
+ ray_scope = ray_scope,
1450
+ ddp_timeout = ddp_timeout,
1451
+ torch_compile = torch_compile,
1452
+ torch_compile_backend = torch_compile_backend,
1453
+ torch_compile_mode = torch_compile_mode,
1454
+ include_tokens_per_second = include_tokens_per_second,
1455
+ include_num_input_tokens_seen = include_num_input_tokens_seen,
1456
+ neftune_noise_alpha = neftune_noise_alpha,
1457
+ optim_target_modules = optim_target_modules,
1458
+ batch_eval_metrics = batch_eval_metrics,
1459
+ eval_on_start = eval_on_start,
1460
+ use_liger_kernel = use_liger_kernel,
1461
+ liger_kernel_config = liger_kernel_config,
1462
+ eval_use_gather_object = eval_use_gather_object,
1463
+ average_tokens_across_devices = average_tokens_across_devices,
1464
+ model_init_kwargs = model_init_kwargs,
1465
+ disable_dropout = disable_dropout,
1466
+ max_prompt_length = max_prompt_length,
1467
+ num_generations = num_generations,
1468
+ max_completion_length = max_completion_length,
1469
+ ds3_gather_for_generation = ds3_gather_for_generation,
1470
+ shuffle_dataset = shuffle_dataset,
1471
+ generation_batch_size = generation_batch_size,
1472
+ steps_per_generation = steps_per_generation,
1473
+ temperature = temperature,
1474
+ top_p = top_p,
1475
+ top_k = top_k,
1476
+ min_p = min_p,
1477
+ generation_kwargs = generation_kwargs,
1478
+ repetition_penalty = repetition_penalty,
1479
+ use_transformers_paged = use_transformers_paged,
1480
+ cache_implementation = cache_implementation,
1481
+ use_vllm = use_vllm,
1482
+ vllm_mode = vllm_mode,
1483
+ vllm_model_impl = vllm_model_impl,
1484
+ vllm_enable_sleep_mode = vllm_enable_sleep_mode,
1485
+ vllm_guided_decoding_regex = vllm_guided_decoding_regex,
1486
+ vllm_server_base_url = vllm_server_base_url,
1487
+ vllm_server_host = vllm_server_host,
1488
+ vllm_server_port = vllm_server_port,
1489
+ vllm_server_timeout = vllm_server_timeout,
1490
+ vllm_gpu_memory_utilization = vllm_gpu_memory_utilization,
1491
+ vllm_tensor_parallel_size = vllm_tensor_parallel_size,
1492
+ beta = beta,
1493
+ num_iterations = num_iterations,
1494
+ epsilon = epsilon,
1495
+ delta = delta,
1496
+ epsilon_high = epsilon_high,
1497
+ importance_sampling_level = importance_sampling_level,
1498
+ reward_weights = reward_weights,
1499
+ scale_rewards = scale_rewards,
1500
+ loss_type = loss_type,
1501
+ mask_truncated_completions = mask_truncated_completions,
1502
+ sync_ref_model = sync_ref_model,
1503
+ ref_model_mixup_alpha = ref_model_mixup_alpha,
1504
+ ref_model_sync_steps = ref_model_sync_steps,
1505
+ top_entropy_quantile = top_entropy_quantile,
1506
+ use_liger_loss = use_liger_loss,
1507
+ vllm_importance_sampling_correction = vllm_importance_sampling_correction,
1508
+ vllm_importance_sampling_cap = vllm_importance_sampling_cap,
1509
+ log_completions = log_completions,
1510
+ num_completions_to_print = num_completions_to_print,
1511
+ wandb_log_unique_prompts = wandb_log_unique_prompts,**kwargs)
1512
+ self.vllm_sampling_params = vllm_sampling_params
1513
+ self.unsloth_num_chunks = unsloth_num_chunks
1514
+
1515
+ pass
1516
+
1517
+ class _UnslothGRPOTrainer(BaseTrainer):
1518
+ """"""
1519
+
1520
+ _tag_names = ["trl", "grpo"]
1521
+ _name = "GRPO"
1522
+ _paper = {
1523
+ "title": "DeepSeekMath: Pushing the Limits of Mathematical Reasoning in Open Language Models",
1524
+ "id": "2402.03300",
1525
+ # docstyle-ignore
1526
+ "citation": textwrap.dedent("""\
1527
+ @article{shao2024deepseekmath,
1528
+ title = {{DeepSeekMath: Pushing the Limits of Mathematical Reasoning in Open Language Models}},
1529
+ author = {Zhihong Shao and Peiyi Wang and Qihao Zhu and Runxin Xu and Junxiao Song and Mingchuan Zhang and Y. K. Li and Y. Wu and Daya Guo},
1530
+ year = 2024,
1531
+ eprint = {arXiv:2402.03300},
1532
+ }
1533
+ """),
1534
+ }
1535
+
1536
+ def __init__(
1537
+ self,
1538
+ model: Union[str, PreTrainedModel],
1539
+ reward_funcs: Union[RewardFunc, list[RewardFunc]],
1540
+ args: Optional[GRPOConfig] = None,
1541
+ train_dataset: Optional[Union[Dataset, IterableDataset]] = None,
1542
+ eval_dataset: Optional[Union[Dataset, IterableDataset, dict[str, Union[Dataset, IterableDataset]]]] = None,
1543
+ processing_class: Optional[Union[PreTrainedTokenizerBase, ProcessorMixin]] = None,
1544
+ reward_processing_classes: Optional[Union[PreTrainedTokenizerBase, list[PreTrainedTokenizerBase]]] = None,
1545
+ callbacks: Optional[list[TrainerCallback]] = None,
1546
+ optimizers: tuple[Optional[torch.optim.Optimizer], Optional[torch.optim.lr_scheduler.LambdaLR]] = (None, None),
1547
+ peft_config: Optional["PeftConfig"] = None,
1548
+ ):
1549
+
1550
+ if hasattr(model, 'vllm_engine') and hasattr(args, 'use_vllm'):
1551
+ if (getattr(args, 'use_vllm', False) == False):
1552
+ args.use_vllm = True
1553
+ args.vllm_mode='colocate'
1554
+ if os.environ.get('UNSLOTH_VLLM_STANDBY', '0') == '1':
1555
+ args.vllm_enable_sleep_mode=True
1556
+ # Args
1557
+ if args is None:
1558
+ model_name = model if isinstance(model, str) else model.config._name_or_path
1559
+ model_name = model_name.split("/")[-1]
1560
+ args = GRPOConfig(f"{model_name}-GRPO")
1561
+
1562
+ # Models
1563
+ # Trained model
1564
+ model_init_kwargs = args.model_init_kwargs or {}
1565
+ if isinstance(model, str):
1566
+ model_id = model
1567
+ dtype = model_init_kwargs.get("dtype")
1568
+ if isinstance(dtype, torch.dtype) or dtype == "auto" or dtype is None:
1569
+ pass # dtype is already a torch.dtype or "auto" or None
1570
+ elif isinstance(dtype, str): # it's a str, but not "auto"
1571
+ dtype = getattr(torch, dtype)
1572
+ model_init_kwargs["dtype"] = dtype
1573
+ else:
1574
+ raise ValueError(
1575
+ "Invalid `dtype` passed to `GRPOConfig`. Expected either 'auto' or a string representing "
1576
+ f"a `torch.dtype` (e.g., 'float32'), but got {dtype}."
1577
+ )
1578
+ # Disable caching if gradient checkpointing is enabled [not supported]
1579
+ config = AutoConfig.from_pretrained(model_id)
1580
+ architecture = getattr(transformers, config.architectures[0])
1581
+ model = architecture.from_pretrained(model_id, **model_init_kwargs)
1582
+ else:
1583
+ model_id = model.config._name_or_path
1584
+ if args.model_init_kwargs is not None:
1585
+ logger.warning(
1586
+ "You passed `model_init_kwargs` to the `GRPOConfig`, but your model is already instantiated. "
1587
+ "The `model_init_kwargs` will be ignored."
1588
+ )
1589
+
1590
+ # Some models [SmolVLM/Idefics3] don't support `logits_to_keep` argument and error out if we pass it
1591
+ # Inspect the forward method before we wrap the model with PEFT
1592
+ self.model_kwarg_keys = (
1593
+ inspect.signature(model.forward).parameters.keys()
1594
+ if not hasattr(model, "get_base_model")
1595
+ else inspect.signature(model.get_base_model().forward).parameters.keys()
1596
+ )
1597
+
1598
+ if False:
1599
+ model = prepare_peft_model(model, peft_config, args)
1600
+
1601
+ # Processing class
1602
+ if processing_class is None:
1603
+ processing_class = AutoProcessor.from_pretrained(model.config._name_or_path, truncation_side="left")
1604
+
1605
+ # Handle pad token for processors or tokenizers
1606
+ if isinstance(processing_class, ProcessorMixin):
1607
+ tokenizer = processing_class.tokenizer
1608
+ elif isinstance(processing_class, PreTrainedTokenizerBase):
1609
+ tokenizer = processing_class
1610
+ else:
1611
+ raise TypeError("The `processing_class` must be either a `PreTrainedTokenizerBase` or a `ProcessorMixin`")
1612
+
1613
+ if tokenizer.pad_token is None:
1614
+ tokenizer.pad_token = tokenizer.eos_token
1615
+
1616
+ self.pad_token = tokenizer.pad_token
1617
+ self.pad_token_id = tokenizer.pad_token_id
1618
+ self.eos_token_id = tokenizer.eos_token_id
1619
+
1620
+ # Reward functions
1621
+ if not isinstance(reward_funcs, list):
1622
+ reward_funcs = [reward_funcs]
1623
+ self.reward_func_names = []
1624
+ for i, reward_func in enumerate(reward_funcs):
1625
+ if isinstance(reward_func, str):
1626
+ reward_funcs[i] = AutoModelForSequenceClassification.from_pretrained(
1627
+ reward_func, num_labels=1, **model_init_kwargs
1628
+ )
1629
+ if isinstance(reward_funcs[i], nn.Module): # Use Module over PretrainedModel for compat w/ compiled models
1630
+ self.reward_func_names.append(reward_funcs[i].config._name_or_path.split("/")[-1])
1631
+ else:
1632
+ self.reward_func_names.append(reward_funcs[i].__name__)
1633
+ self.reward_funcs = reward_funcs
1634
+
1635
+ # Reward weights
1636
+ if args.reward_weights is not None:
1637
+ if len(args.reward_weights) != len(reward_funcs):
1638
+ raise ValueError(
1639
+ f"Number of reward weights ({len(args.reward_weights)}) must match number of reward "
1640
+ f"functions ({len(reward_funcs)})"
1641
+ )
1642
+ self.reward_weights = torch.tensor(args.reward_weights, dtype=torch.float32)
1643
+ else:
1644
+ self.reward_weights = torch.ones(len(reward_funcs), dtype=torch.float32)
1645
+
1646
+ # Reward processing class
1647
+ if reward_processing_classes is None:
1648
+ reward_processing_classes = [None] * len(reward_funcs)
1649
+ elif not isinstance(reward_processing_classes, list):
1650
+ reward_processing_classes = [reward_processing_classes]
1651
+ if len(reward_processing_classes) != len(reward_funcs):
1652
+ raise ValueError(
1653
+ f"The number of reward processing classes ({len(reward_processing_classes)}) must match the number of "
1654
+ f"reward functions ({len(reward_funcs)})."
1655
+ )
1656
+
1657
+ for i, (reward_processing_class, reward_func) in enumerate(zip(reward_processing_classes, reward_funcs)):
1658
+ if isinstance(reward_func, PreTrainedModel):
1659
+ if reward_processing_class is None:
1660
+ reward_processing_class = AutoTokenizer.from_pretrained(reward_func.config._name_or_path)
1661
+ if reward_processing_class.pad_token_id is None:
1662
+ reward_processing_class.pad_token = reward_processing_class.eos_token
1663
+ # The reward model computes the reward for the latest non-padded token in the input sequence.
1664
+ # So it's important to set the pad token ID to the padding token ID of the processing class.
1665
+ reward_func.config.pad_token_id = reward_processing_class.pad_token_id
1666
+ reward_processing_classes[i] = reward_processing_class
1667
+
1668
+ self.reward_processing_classes = reward_processing_classes
1669
+
1670
+ # Training arguments
1671
+ self.max_prompt_length = args.max_prompt_length
1672
+ self.max_completion_length = args.max_completion_length # = |o_i| in the GRPO paper
1673
+ self.num_generations = args.num_generations # = G in the GRPO paper
1674
+ self.temperature = args.temperature
1675
+ self.top_p = args.top_p
1676
+ self.top_k = args.top_k
1677
+ self.min_p = args.min_p
1678
+ self.repetition_penalty = args.repetition_penalty
1679
+ self.use_transformers_paged = args.use_transformers_paged
1680
+ self.use_vllm = args.use_vllm
1681
+ self.vllm_mode = args.vllm_mode
1682
+ self.vllm_gpu_memory_utilization = args.vllm_gpu_memory_utilization # only applies to colocation mode
1683
+ self.vllm_tensor_parallel_size = args.vllm_tensor_parallel_size # only applies to colocation mode
1684
+ self.vllm_importance_sampling_correction = args.vllm_importance_sampling_correction
1685
+ self.vllm_importance_sampling_cap = args.vllm_importance_sampling_cap
1686
+ self.use_liger_loss = args.use_liger_loss
1687
+ self.loss_type = args.loss_type
1688
+ self.scale_rewards = args.scale_rewards
1689
+ self.importance_sampling_level = args.importance_sampling_level
1690
+ self.mask_truncated_completions = args.mask_truncated_completions
1691
+ self.top_entropy_quantile = args.top_entropy_quantile
1692
+ if self.use_liger_loss and self.top_entropy_quantile < 1.0:
1693
+ raise NotImplementedError(
1694
+ "Liger Kernels don't currently support masking token positions based on entropy."
1695
+ )
1696
+ if self.use_liger_loss and not self.importance_sampling_level == "token":
1697
+ raise NotImplementedError(
1698
+ "Liger Kernels currently only support token-level importance sampling. Please set"
1699
+ "`importance_sampling_level` to 'token'."
1700
+ )
1701
+
1702
+ # Datasets
1703
+ self.shuffle_dataset = args.shuffle_dataset
1704
+
1705
+ if (
1706
+ isinstance(train_dataset, IterableDataset)
1707
+ or isinstance(eval_dataset, IterableDataset)
1708
+ or (
1709
+ isinstance(eval_dataset, dict) and any(isinstance(ds, IterableDataset) for ds in eval_dataset.values())
1710
+ )
1711
+ ):
1712
+ # See https://github.com/huggingface/trl/issues/3213
1713
+ raise NotImplementedError(
1714
+ "Iterable datasets are not yet supported in GRPOTrainer. Please use a standard dataset instead."
1715
+ )
1716
+
1717
+ # Multi-step
1718
+ self.num_iterations = args.num_iterations # = 𝜇 in the GRPO paper
1719
+ self.epsilon_low = args.epsilon
1720
+ self.epsilon_high = args.epsilon_high if args.epsilon_high is not None else args.epsilon
1721
+ # Tracks the number of iterations [forward + backward passes], including those within a grad accum cycle
1722
+ self._step = 0
1723
+ # Buffer the batch to reuse generated outputs across multiple updates. For more details, see
1724
+ # `_get_train_sampler` and `_prepare_inputs`.
1725
+ self._buffered_inputs = None
1726
+
1727
+ # The trainer estimates the number of FLOPs [floating-point operations] using the number of elements in the
1728
+ # input tensor associated with the key "input_ids". However, in GRPO, the sampled data does not include the
1729
+ # "input_ids" key. Instead, the available keys is "prompt". As a result, the trainer issues the warning:
1730
+ # "Could not estimate the number of tokens of the input, floating-point operations will not be computed." To
1731
+ # suppress this warning, we set the "estimate_tokens" key in the model's "warnings_issued" dictionary to True.
1732
+ # This acts as a flag to indicate that the warning has already been issued.
1733
+ model.warnings_issued["estimate_tokens"] = True
1734
+
1735
+ super().__init__(
1736
+ model=model,
1737
+ args=args,
1738
+ data_collator=identity, # No data collation is needed in GRPO
1739
+ train_dataset=train_dataset,
1740
+ eval_dataset=eval_dataset,
1741
+ processing_class=processing_class,
1742
+ callbacks=callbacks,
1743
+ optimizers=optimizers,
1744
+ # In Trainer, `training_step` scales the loss by `gradient_accumulation_steps` only if `compute_loss_func`
1745
+ # is None. For DAPO, loss scaling instead depends on the total number of completions tokens across the
1746
+ # global accumulated batch. To control scaling ourselves, we must disable Trainer’s built-in scaling. The
1747
+ # simplest [though a bit hacky] way is to set `compute_loss_func` to any non-None value, which bypasses
1748
+ # that behavior without rewriting `training_step`.
1749
+ compute_loss_func="non-None value to disable scaling",
1750
+ )
1751
+
1752
+ # Reference model
1753
+ self.beta = args.beta
1754
+ if self.beta == 0.0:
1755
+ # If beta is 0.0, the reference model is not needed
1756
+ self.ref_model = None
1757
+ elif is_peft_model(model):
1758
+ # If PEFT is used, the reference model is not needed since the adapter can be disabled
1759
+ # to revert to the initial model.
1760
+ self.ref_model = None
1761
+ else:
1762
+ # For deepspeed, fsdp or non-distributed models, create a reference model from scratch
1763
+ config = AutoConfig.from_pretrained(model_id)
1764
+ architecture = getattr(transformers, config.architectures[0])
1765
+ self.ref_model = architecture.from_pretrained(model_id, **model_init_kwargs)
1766
+
1767
+ # Disable dropout in the models
1768
+ if args.disable_dropout:
1769
+ disable_dropout_in_model(model)
1770
+ if self.ref_model is not None:
1771
+ disable_dropout_in_model(self.ref_model)
1772
+
1773
+ # Liger loss
1774
+ if self.use_liger_loss:
1775
+ if not is_liger_kernel_available():
1776
+ raise ImportError(
1777
+ "Liger is required to use `liger_loss` as the GRPO loss. Run `pip install liger-kernel`."
1778
+ )
1779
+ # redirect the model.module forward to the model forward to ensure pre-forward hooks are called
1780
+ self._forward_redirection = _ForwardRedirection()
1781
+
1782
+ self.liger_grpo_loss = LigerFusedLinearGRPOLoss(
1783
+ beta=self.beta,
1784
+ epsilon_low=self.epsilon_low,
1785
+ epsilon_high=self.epsilon_high,
1786
+ temperature=self.temperature,
1787
+ use_ref_model=self.beta != 0.0,
1788
+ loss_type=self.loss_type,
1789
+ max_completion_length=self.max_completion_length,
1790
+ )
1791
+
1792
+ # Initialize the metrics
1793
+ self._metrics = {"train": defaultdict(list), "eval": defaultdict(list)}
1794
+ self._total_train_tokens = 0
1795
+ self.log_completions = args.log_completions
1796
+ self.wandb_log_unique_prompts = args.wandb_log_unique_prompts
1797
+ self.num_completions_to_print = args.num_completions_to_print
1798
+ # Keep logs sized to the generation batch to record only outputs from the latest model update.
1799
+ self._logs = {
1800
+ "images": deque(maxlen=args.generation_batch_size),
1801
+ "prompt": deque(maxlen=args.generation_batch_size),
1802
+ "completion": deque(maxlen=args.generation_batch_size),
1803
+ "rewards": defaultdict(lambda: deque(maxlen=args.generation_batch_size)),
1804
+ "advantages": deque(maxlen=args.generation_batch_size),
1805
+ }
1806
+
1807
+ # Ensure each process receives a unique seed to prevent duplicate completions when generating with
1808
+ # transformers if num_generations exceeds per_device_train_batch_size. We could skip it if we use vLLM, but
1809
+ # it's safer to set it in all cases.
1810
+ set_seed(args.seed, device_specific=True)
1811
+
1812
+ if self.use_vllm:
1813
+ if not is_vllm_available():
1814
+ raise ImportError(
1815
+ "vLLM is not available and `use_vllm` is set to True. Please install vLLM with "
1816
+ "`pip install trl[vllm]` to use it."
1817
+ )
1818
+
1819
+ if self.vllm_mode == "server":
1820
+ if self.accelerator.is_main_process:
1821
+ if args.vllm_server_base_url is not None:
1822
+ base_url = args.vllm_server_base_url
1823
+ else:
1824
+ base_url = f"http://{args.vllm_server_host}:{args.vllm_server_port}"
1825
+ self.vllm_client = VLLMClient(base_url=base_url, connection_timeout=args.vllm_server_timeout)
1826
+ self.vllm_client.init_communicator(device=torch.cuda.current_device())
1827
+
1828
+ elif self.vllm_mode == "colocate":
1829
+ if not self.accelerator.num_processes % self.vllm_tensor_parallel_size == 0:
1830
+ raise ValueError(
1831
+ f"vllm_tensor_parallel_size ({self.vllm_tensor_parallel_size}) must divide world size "
1832
+ f"({self.accelerator.num_processes}) evenly."
1833
+ )
1834
+
1835
+ if self.vllm_tensor_parallel_size > 1:
1836
+ self.tp_group, _ = torch.distributed.new_subgroups_by_enumeration(
1837
+ [
1838
+ list(range(i * self.vllm_tensor_parallel_size, (i + 1) * self.vllm_tensor_parallel_size))
1839
+ for i in range(self.accelerator.num_processes // self.vllm_tensor_parallel_size)
1840
+ ]
1841
+ )
1842
+ os.environ["RANK"] = str(self.accelerator.process_index)
1843
+ os.environ["LOCAL_RANK"] = str(self.accelerator.local_process_index)
1844
+ os.environ["WORLD_SIZE"] = str(self.accelerator.num_processes)
1845
+ ensure_master_addr_port()
1846
+
1847
+ if self.max_prompt_length is not None and self.max_completion_length is not None:
1848
+ max_model_len = self.max_prompt_length + self.max_completion_length
1849
+ else:
1850
+ max_model_len = None
1851
+ self.llm = model.vllm_engine
1852
+ if self.args.vllm_enable_sleep_mode:
1853
+ self.llm.sleep(level=1)
1854
+ else:
1855
+ raise ValueError(f"vllm_mode must be either 'server' or 'colocate', got '{self.vllm_mode}'.")
1856
+ self.guided_decoding_regex = args.vllm_guided_decoding_regex
1857
+
1858
+ self._last_loaded_step = -1
1859
+ self.accelerator.wait_for_everyone()
1860
+ else:
1861
+ generation_kwargs = {
1862
+ "max_new_tokens": self.max_completion_length,
1863
+ "do_sample": True,
1864
+ "pad_token_id": tokenizer.pad_token_id,
1865
+ "bos_token_id": tokenizer.bos_token_id,
1866
+ "eos_token_id": tokenizer.eos_token_id,
1867
+ "temperature": self.temperature,
1868
+ "top_p": self.top_p,
1869
+ "top_k": self.top_k,
1870
+ "min_p": self.min_p,
1871
+ "repetition_penalty": self.repetition_penalty,
1872
+ "cache_implementation": args.cache_implementation,
1873
+ }
1874
+ if args.generation_kwargs is not None:
1875
+ generation_kwargs.update(args.generation_kwargs)
1876
+ self.generation_config = GenerationConfig(**generation_kwargs)
1877
+
1878
+ # Gradient accumulation requires scaled loss. Normally, loss scaling in the parent class depends on whether the
1879
+ # model accepts loss-related kwargs. Since we compute our own loss, this check is irrelevant. We set
1880
+ # self.model_accepts_loss_kwargs to False to enable scaling.
1881
+ self.model_accepts_loss_kwargs = False
1882
+
1883
+ # Add tags to the model
1884
+ self.model.add_model_tags(self._tag_names)
1885
+
1886
+ if self.ref_model is not None:
1887
+ if self.is_deepspeed_enabled:
1888
+ self.ref_model = prepare_deepspeed(self.ref_model, self.accelerator)
1889
+ elif self.is_fsdp_enabled:
1890
+ self.ref_model = prepare_fsdp(self.ref_model, self.accelerator)
1891
+ else:
1892
+ self.ref_model = self.accelerator.prepare_model(self.ref_model, evaluation_mode=True)
1893
+
1894
+ if args.sync_ref_model:
1895
+ self.add_callback(SyncRefModelCallback(ref_model=self.ref_model, accelerator=self.accelerator))
1896
+
1897
+ for i, reward_func in enumerate(self.reward_funcs):
1898
+ if isinstance(reward_func, PreTrainedModel):
1899
+ if self.is_deepspeed_enabled:
1900
+ self.reward_funcs[i] = prepare_deepspeed(reward_func, self.accelerator)
1901
+ else:
1902
+ # set device placement to True to make `prepare_model` move `reward_func` to device when using fsdp
1903
+ self.reward_funcs[i] = self.accelerator.prepare_model(
1904
+ reward_func, evaluation_mode=True, device_placement=True
1905
+ )
1906
+
1907
+ def _set_signature_columns_if_needed(self):
1908
+ # If `self.args.remove_unused_columns` is True, non-signature columns are removed.
1909
+ # By default, this method sets `self._signature_columns` to the model's expected inputs.
1910
+ # In GRPOTrainer, we preprocess data, so using the model's signature columns doesn't work.
1911
+ # Instead, we set them to the columns expected by the `training_step` method, hence the override.
1912
+ if self._signature_columns is None:
1913
+ self._signature_columns = ["prompt", "image", "images"]
1914
+
1915
+ # This method overrides `Trainer.get_train_dataloader` to support our custom batching strategy.
1916
+ # Instead of returning a standard per-step batch (i.e., `per_device_batch_size), our dataloader loads an
1917
+ # *generation* batch (i.e., `per_device_batch_size × steps_per_generation`). This allows us to generate completions
1918
+ # once every steps_per_generation step—rather than once per accumulation step—which is significantly more
1919
+ # efficient. The only change from the original implementation is multiplying the batch size by
1920
+ # `steps_per_generation`. Thus, `_prepare_inputs` is called with this *generation* batch, and it handles the
1921
+ # splitting internally.
1922
+ # Maintenance note: This method is a copy-paste of the original `Trainer.get_train_dataloader` with only one line
1923
+ # modification. As a result, some parts of the method aren't relevant to GRPO, but we keep them to stay one line
1924
+ # apart from the super method, ensuring easier maintenance in the future.
1925
+ def get_train_dataloader(self):
1926
+ if self.train_dataset is None:
1927
+ raise ValueError("Trainer: training requires a train_dataset.")
1928
+
1929
+ train_dataset = self.train_dataset
1930
+ data_collator = self.data_collator
1931
+ if is_datasets_available() and isinstance(train_dataset, datasets.Dataset):
1932
+ train_dataset = self._remove_unused_columns(train_dataset, description="training")
1933
+ else:
1934
+ data_collator = self._get_collator_with_removed_columns(data_collator, description="training")
1935
+
1936
+ dataloader_params = {
1937
+ "batch_size": self._train_batch_size * self.args.steps_per_generation, # < this is the change
1938
+ "collate_fn": data_collator,
1939
+ "num_workers": self.args.dataloader_num_workers,
1940
+ "pin_memory": self.args.dataloader_pin_memory,
1941
+ "persistent_workers": self.args.dataloader_persistent_workers,
1942
+ }
1943
+
1944
+ if not isinstance(train_dataset, torch.utils.data.IterableDataset):
1945
+ dataloader_params["sampler"] = self._get_train_sampler()
1946
+ dataloader_params["drop_last"] = self.args.dataloader_drop_last
1947
+ dataloader_params["worker_init_fn"] = partial(
1948
+ seed_worker, num_workers=self.args.dataloader_num_workers, rank=self.args.process_index
1949
+ )
1950
+
1951
+ dataloader_params["prefetch_factor"] = self.args.dataloader_prefetch_factor
1952
+
1953
+ return self.accelerator.prepare(DataLoader(train_dataset, **dataloader_params))
1954
+
1955
+ def _get_train_sampler(self, dataset: Optional[Dataset] = None) -> Sampler:
1956
+ # Returns a sampler that
1957
+ # 1. ensures each prompt is repeated across multiple processes. This guarantees that identical prompts are
1958
+ # distributed to different GPUs, allowing rewards to be computed and normalized correctly within each prompt
1959
+ # group. Using the same seed across processes ensures consistent prompt assignment, preventing discrepancies
1960
+ # in group formation.
1961
+ # 2. repeats the batch multiple times to allow reusing generations across multiple updates. Refer to
1962
+ # _prepare_inputs to see how the generations are stored and reused.
1963
+
1964
+ # In the following figure, the values are the prompt indices. The first row shows the first sampled batch, the
1965
+ # second row shows the second sampled batch, and so on.
1966
+ #
1967
+ # | GPU 0 | GPU 1 |
1968
+ #
1969
+ # global_step step <-───> num_generations=2
1970
+ # <-───────> per_device_train_batch_size=3
1971
+ # grad_accum ▲ ▲ 0 0 0 0 1 1 2 2 <- Generate for the first `steps_per_generation` (prompts 0 to 11); store the completions; use the first slice to compute the loss
1972
+ # =2 ▼ | 0 1 3 3 4 4 5 5 <- Take the stored generations and use the second slice to compute the loss
1973
+ # |
1974
+ # | 1 2 6 6 7 7 8 8 <- Take the stored generations and use the third slice to compute the loss
1975
+ # steps_per_gen=4 ▼ 1 3 9 9 10 10 11 11 <- Take the stored generations and use the fourth slice to compute the loss
1976
+ #
1977
+ # 2 4 12 12 13 13 14 14 <- Generate for the second `steps_per_generation` (prompts 12 to 23); store the completions; use the first slice to compute the loss
1978
+ # 2 5 15 15 16 16 17 17 <- Take the stored generations and use the second slice to compute the loss
1979
+ # ...
1980
+ if dataset is None:
1981
+ dataset = self.train_dataset
1982
+ return RepeatSampler(
1983
+ data_source=dataset,
1984
+ mini_repeat_count=self.num_generations,
1985
+ batch_size=self.args.generation_batch_size // self.num_generations,
1986
+ repeat_count=self.num_iterations * self.args.steps_per_generation,
1987
+ shuffle=self.shuffle_dataset,
1988
+ seed=self.args.seed,
1989
+ )
1990
+
1991
+ def _get_eval_sampler(self, eval_dataset) -> Sampler:
1992
+ # See _get_train_sampler for an explanation of the sampler.
1993
+ return RepeatSampler(
1994
+ data_source=eval_dataset,
1995
+ mini_repeat_count=self.num_generations,
1996
+ seed=self.args.seed,
1997
+ )
1998
+
1999
+ @profiling_decorator
2000
+ def _get_last_hidden_state(
2001
+ self,
2002
+ unwrapped_model,
2003
+ input_ids,
2004
+ attention_mask,
2005
+ logits_to_keep,
2006
+ pixel_values=None,
2007
+ image_grid_thw=None,
2008
+ pixel_attention_mask=None,
2009
+ image_sizes=None,
2010
+ ):
2011
+ if is_peft_model(unwrapped_model):
2012
+ unwrapped_model = unwrapped_model.base_model.model
2013
+
2014
+ # Build model inputs - check if the model supports logits_to_keep (some models and VLMs don't)
2015
+ model_inputs = {"input_ids": input_ids, "attention_mask": attention_mask}
2016
+
2017
+ # For Qwen models:
2018
+ if image_grid_thw is not None and pixel_values is not None:
2019
+ model_inputs["image_grid_thw"] = image_grid_thw
2020
+ # For Gemma, SmolVLM2, LLaVa-Next etc.:
2021
+ if pixel_values is not None:
2022
+ model_inputs["pixel_values"] = pixel_values
2023
+ # For SmolVLM2
2024
+ if pixel_attention_mask is not None:
2025
+ model_inputs["pixel_attention_mask"] = pixel_attention_mask
2026
+ # For LLaVa-Next
2027
+ if image_sizes is not None:
2028
+ model_inputs["image_sizes"] = image_sizes
2029
+
2030
+ # Only add logits_to_keep if the model supports it
2031
+ if "logits_to_keep" in self.model_kwarg_keys:
2032
+ # We add 1 to `logits_to_keep` because the last logits of the sequence is later excluded
2033
+ model_inputs["logits_to_keep"] = logits_to_keep + 1
2034
+
2035
+ model_inputs["use_cache"] = False # only used in generation; set False to suppress warnings
2036
+
2037
+ last_hidden_state = unwrapped_model.model(**model_inputs).last_hidden_state
2038
+ # Exclude the last value: it corresponds to the next token pred
2039
+ last_hidden_state = last_hidden_state[:, :-1, :] # (B, L-1, H)
2040
+ # Only keep the last logits_to_keep. For model that support logits_to_keep, this is a no-op.
2041
+ last_hidden_state = last_hidden_state[:, -logits_to_keep:, :] # (B, logits_to_keep, H)
2042
+ return last_hidden_state
2043
+
2044
+ def get_high_entropy_mask(self, entropies: torch.Tensor, mask: torch.Tensor, threshold: float) -> torch.Tensor:
2045
+ """
2046
+ Returns a binary mask identifying tokens whose entropy exceeds a given quantile threshold.
2047
+
2048
+ Args:
2049
+ entropies (`torch.Tensor`):
2050
+ Tensor of shape (batch_size, seq_len) with per-token entropy values.
2051
+ mask (`torch.Tensor`):
2052
+ Binary mask of the same shape as `entropies`, where `1` indicates valid tokens and `0` padding.
2053
+ threshold (`float`):
2054
+ Quantile threshold between `0.0` and `1.0` to select high-entropy tokens.
2055
+
2056
+ Returns:
2057
+ `torch.Tensor`:
2058
+ Boolean mask of shape (batch_size, seq_len), where `True` indicates tokens with entropy >= threshold
2059
+ and `False` otherwise.
2060
+ """
2061
+ local = entropies[mask.bool()].float()
2062
+
2063
+ # Use a negative pad_value as a sentinel because entropy values are always >= 0.
2064
+ # This guarantees that the sentinel cannot collide with any real entropy value.
2065
+ pad_value = -1e9
2066
+
2067
+ # Pad across processes so that every rank has the same tensor length
2068
+ padded = self.accelerator.pad_across_processes(local, dim=0, pad_index=pad_value)
2069
+ gathered = self.accelerator.gather(padded)
2070
+
2071
+ # Drop sentinel values (safe because no entropy can be negative)
2072
+ gathered = gathered[gathered != pad_value]
2073
+
2074
+ if gathered.numel() == 0:
2075
+ return torch.zeros_like(entropies, dtype=torch.bool)
2076
+
2077
+ entropy_threshold = torch.quantile(gathered, threshold)
2078
+ masked_entropies = entropies * mask.float()
2079
+ entropy_mask = masked_entropies >= entropy_threshold
2080
+ return entropy_mask & mask.bool() # ensure padding tokens are always masked out
2081
+
2082
+ def _get_per_token_logps_and_entropies(
2083
+ self,
2084
+ model,
2085
+ input_ids,
2086
+ attention_mask,
2087
+ logits_to_keep,
2088
+ batch_size = None,
2089
+ compute_entropy = False,
2090
+ compute_efficient = False,
2091
+ *args,
2092
+ **kwargs,
2093
+ ):
2094
+ # if True: # os.environ.get('UNSLOTH_USE_NEW_MODEL', '0') == '0':
2095
+ # return None, None # logps, entropies Unsloth efficient GRPO
2096
+ if compute_efficient:
2097
+ return None, None
2098
+ else:
2099
+ # Otherwise, calculate normally:
2100
+ if not hasattr(self, "_autocast_dtype"):
2101
+ self._autocast_dtype = (
2102
+ torch.float16
2103
+ if os.environ.get("ACCELERATE_MIXED_PRECISION", "fp16") == "fp16"
2104
+ else torch.bfloat16
2105
+ )
2106
+ if os.environ.get("UNSLOTH_FORCE_FLOAT32", "0") == "1":
2107
+ self._autocast_dtype = torch.float16
2108
+
2109
+ pixel_values, image_grid_thw = (
2110
+ kwargs.get("pixel_values", None),
2111
+ kwargs.get("image_grid_thw", None),
2112
+ )
2113
+ pixel_attention_mask, image_sizes = (
2114
+ kwargs.get("pixel_attention_mask", None),
2115
+ kwargs.get("image_sizes", None),
2116
+ )
2117
+
2118
+ os.environ["UNSLOTH_RETURN_HIDDEN_STATES"] = "1"
2119
+
2120
+ unwrapped_model = self.accelerator.unwrap_model(
2121
+ model, keep_fp32_wrapper = False
2122
+ )
2123
+
2124
+ with torch.amp.autocast(device_type = "cuda", dtype = self._autocast_dtype):
2125
+ with _get_inference_mode_context_manager(model):
2126
+ if pixel_values is None:
2127
+ attention_mask = input_ids != self.processing_class.pad_token_id
2128
+ attention_mask = attention_mask.to(attention_mask.dtype)
2129
+ # We add 1 to `logits_to_keep` because the last logits of the sequence is later excluded
2130
+ logits = unwrapped_model(
2131
+ input_ids = input_ids,
2132
+ attention_mask = attention_mask,
2133
+ pixel_values = pixel_values,
2134
+ image_grid_thw = image_grid_thw,
2135
+ pixel_attention_mask = pixel_attention_mask,
2136
+ image_sizes = image_sizes,
2137
+ # logits_to_keep = logits_to_keep + 1,
2138
+ ).logits
2139
+ else:
2140
+ logits = unwrapped_model(
2141
+ input_ids = input_ids,
2142
+ attention_mask = attention_mask,
2143
+ pixel_values = pixel_values,
2144
+ image_grid_thw = image_grid_thw,
2145
+ pixel_attention_mask = pixel_attention_mask,
2146
+ image_sizes = image_sizes,
2147
+ logits_to_keep = logits_to_keep + 1,
2148
+ ).logits
2149
+
2150
+ entropies = None
2151
+ if compute_entropy:
2152
+ from trl.trainer.utils import entropy_from_logits
2153
+
2154
+ entropies = entropy_from_logits(logits)
2155
+
2156
+ os.environ["UNSLOTH_RETURN_HIDDEN_STATES"] = "0"
2157
+ # logits = logits[:, :-1, :] # (B, L-1, V), exclude the last logit: it corresponds to the next token pred
2158
+ return logits.detach(), entropies # logps, entropies
2159
+ # input_ids = input_ids[:, -logits_to_keep:]
2160
+ # For transformers<=4.48, logits_to_keep argument isn't supported, so here we drop logits ourselves.
2161
+ # See https://github.com/huggingface/trl/issues/2770
2162
+ # logits = logits[:, -logits_to_keep:]
2163
+ # return logits
2164
+ # See https://huggingface.co/blog/the_n_implementation_details_of_rlhf_with_ppo#policy-training-implementation-details
2165
+ # logits = logits / self.temperature
2166
+ # logps = selective_log_softmax(logits, input_ids)
2167
+
2168
+ # row_indices, col_indices = torch.where(logps < -20)
2169
+
2170
+ # # Method 1: Check if tensors have elements
2171
+ # if len(row_indices) > 0 and len(col_indices) > 0:
2172
+ # breakpoint() # Breakpoint triggered here
2173
+ # print("Found high values!")
2174
+ # return logps # compute logprobs for the input tokens
2175
+
2176
+ def _fix_param_name_to_vllm(self, name, extra_prefixes: Optional[list[str]] = None):
2177
+ extra_prefixes = extra_prefixes or []
2178
+ prefixes = ["_checkpoint_wrapped_module."] + extra_prefixes
2179
+ for prefix in prefixes:
2180
+ name = name.replace(prefix, "")
2181
+ return name
2182
+
2183
+ def _sync_fsdp1_params_to_vllm(self, module: nn.Module, prefix: str = "", visited=None):
2184
+ """Memory-efficient post-order traversal of FSDP modules to extract full parameters and sync with vLLM."""
2185
+ # For FSDP1, we need to recurse into children and also use summon_full_params
2186
+ if visited is None:
2187
+ visited = set()
2188
+ for child_name, child_module in module.named_children():
2189
+ child_prefix = f"{prefix}.{child_name}" if prefix else child_name
2190
+ self._sync_fsdp1_params_to_vllm(
2191
+ child_module, prefix=child_prefix, visited=visited
2192
+ ) # recurse into the child
2193
+
2194
+ if isinstance(module, FSDP):
2195
+ with FSDP.summon_full_params(module, recurse=False, writeback=False):
2196
+ for param_name, param in module.named_parameters():
2197
+ full_name = f"{prefix}.{param_name}" if prefix else param_name
2198
+ full_name = self._fix_param_name_to_vllm(full_name, extra_prefixes=["_fsdp_wrapped_module."])
2199
+
2200
+ if full_name in visited:
2201
+ continue # skip FSDP subtrees already traversed
2202
+ visited.add(full_name)
2203
+
2204
+ if self.vllm_mode == "server" and self.accelerator.is_main_process:
2205
+ self.vllm_client.update_named_param(full_name, param.data)
2206
+ elif self.vllm_mode == "colocate":
2207
+
2208
+ pass
2209
+
2210
+ pass
2211
+
2212
+ def _sync_fsdp2_params_to_vllm(self, module: nn.Module):
2213
+ # For FSDP2, module already covers all parameters, so no need for recursion
2214
+ for name, param in module.items():
2215
+ if param.is_cpu:
2216
+ param = param.to(torch.device("cuda"))
2217
+ param = param.full_tensor()
2218
+
2219
+ if self.vllm_mode == "server" and self.accelerator.is_main_process:
2220
+ self.vllm_client.update_named_param(name, param)
2221
+ elif self.vllm_mode == "colocate":
2222
+
2223
+ pass
2224
+
2225
+ pass
2226
+
2227
+ def _move_model_to_vllm(self, *args, **kwargs):
2228
+ return None
2229
+
2230
+ @profiling_decorator
2231
+ def _prepare_inputs(
2232
+ self, generation_batch: dict[str, Union[torch.Tensor, Any]]
2233
+ ) -> dict[str, Union[torch.Tensor, Any]]:
2234
+ # Prepares inputs for model training/evaluation by managing completion generation and batch handling.
2235
+ # During training:
2236
+ # - Receives the local generation batch (Per-GPU batch size × steps per generation)
2237
+ # from the modified training dataloader instead of the standard local batch
2238
+ # - Generates completions once for the entire generation batch and splits it into batches of size
2239
+ # `per_device_train_batch_size`
2240
+ # - Buffers these completions and returns the appropriate slice for the current accumulation step
2241
+ # - Optimizes by regenerating completions only periodically (every steps_per_generation * num_iterations)
2242
+ # During evaluation:
2243
+ # - The input is treated as a standard local batch (no accumulation, no multiple iterations)
2244
+ # - Completions are generated for each batch without buffering or reuse
2245
+ # Returns a single local batch in both cases.
2246
+
2247
+ mode = "train" if self.model.training else "eval"
2248
+ if mode == "train":
2249
+ generate_every = self.args.steps_per_generation * self.num_iterations
2250
+ if self._step % generate_every == 0 or self._buffered_inputs is None:
2251
+ # self._buffered_inputs=None can occur when resuming from a checkpoint
2252
+ generation_batch = self._generate_and_score_completions(generation_batch)
2253
+ generation_batch = split_pixel_values_by_grid(generation_batch)
2254
+
2255
+ try: generation_batch = shuffle_sequence_dict(generation_batch)
2256
+
2257
+ except: pass
2258
+ generation_batches = split_tensor_dict(generation_batch, self.args.steps_per_generation)
2259
+ self._buffered_inputs = [unsplit_pixel_values_by_grid(batch) for batch in generation_batches]
2260
+ inputs = self._buffered_inputs[self._step % self.args.steps_per_generation]
2261
+ self._step += 1
2262
+ else:
2263
+ # In evaluation, there is neither batch grouping for generation, nor multiple iterations, hence
2264
+ # local generation batch == local eval batch
2265
+ inputs = self._generate_and_score_completions(generation_batch)
2266
+ return inputs
2267
+
2268
+ @profiling_decorator
2269
+ def _calculate_rewards(self, inputs, prompts, completions, completion_ids_list):
2270
+ device = self.accelerator.device
2271
+ rewards_per_func = torch.zeros(len(prompts), len(self.reward_funcs), device=device)
2272
+
2273
+ # Repeat all input columns (but "prompt", "completion", and "completion_ids") to match the num of generations
2274
+ keys = [key for key in inputs[0] if key not in ["prompt", "completion", "completion_ids"]]
2275
+ reward_kwargs = {key: [example[key] for example in inputs] for key in keys}
2276
+
2277
+ # This allows for dynamic reward shaping based on training progress.
2278
+ reward_kwargs["trainer_state"] = self.state
2279
+
2280
+ for i, (reward_func, reward_processing_class, reward_func_name) in enumerate(
2281
+ zip(self.reward_funcs, self.reward_processing_classes, self.reward_func_names)
2282
+ ):
2283
+ with profiling_context(self, reward_func_name):
2284
+ if isinstance(reward_func, nn.Module): # Module (no PretrainedModel) for compat with compiled models
2285
+ if is_conversational(inputs[0]):
2286
+ messages = [{"messages": p + c} for p, c in zip(prompts, completions)]
2287
+ texts = [apply_chat_template(x, reward_processing_class)["text"] for x in messages]
2288
+ else:
2289
+ texts = [p + c for p, c in zip(prompts, completions)]
2290
+ reward_inputs = reward_processing_class(
2291
+ text=texts, return_tensors="pt", padding=True, padding_side="right", add_special_tokens=False
2292
+ )
2293
+ reward_inputs = super()._prepare_inputs(reward_inputs)
2294
+ with torch.inference_mode():
2295
+ rewards_per_func[:, i] = reward_func(**reward_inputs).logits[:, 0] # Shape (B*G,)
2296
+ else:
2297
+ output_reward_func = reward_func(
2298
+ prompts=prompts, completions=completions, completion_ids=completion_ids_list, **reward_kwargs
2299
+ )
2300
+ # Convert None values to NaN
2301
+ output_reward_func = [reward if reward is not None else torch.nan for reward in output_reward_func]
2302
+
2303
+ rewards_per_func[:, i] = torch.tensor(output_reward_func, dtype=torch.float32, device=device)
2304
+
2305
+ # If all reward functions return None for a given row, issue a detailed warning
2306
+ if torch.isnan(rewards_per_func).all(dim=1).any():
2307
+ nan_row_idx = torch.isnan(rewards_per_func).all(dim=1).nonzero(as_tuple=True)[0][0]
2308
+ row_reward_kwargs = {
2309
+ key: value[nan_row_idx] for key, value in reward_kwargs.items() if key != "trainer_state"
2310
+ }
2311
+ row_reward_kwargs["prompt"] = prompts[nan_row_idx]
2312
+ row_reward_kwargs["completion"] = completions[nan_row_idx]
2313
+ logger.warning(
2314
+ f"All reward functions returned None for the following kwargs:\n{row_reward_kwargs}\n"
2315
+ "Please ensure that at least one reward function returns a valid reward."
2316
+ )
2317
+
2318
+ # Gather the reward per function: this part is crucial, because the rewards are normalized per group and the
2319
+ # completions may be distributed across processes
2320
+ rewards_per_func = gather(rewards_per_func)
2321
+ return rewards_per_func
2322
+
2323
+ def _generate_single_turn(self, prompts: list[str], images: Optional[list]):
2324
+ device = self.accelerator.device
2325
+
2326
+ # If the prompts are conversational and the inputs contain images, we need to convert the prompts from
2327
+ # [{"role": "user", "content": "What color is the sky?"}] to
2328
+ # [{"role": "user", "content": [{"type": "image"}, {"type": "text", "text": "What color is the sky?"}]}]
2329
+ kwargs = {}
2330
+ if images is not None:
2331
+ kwargs = {"images": images}
2332
+ for prompt, image_list in zip(prompts, images):
2333
+ if isinstance(prompt, list): # i.e., when using conversational data
2334
+ prepare_multimodal_messages(prompt, num_images=len(image_list))
2335
+
2336
+
2337
+ _chat_template_ = getattr(self.processing_class, "chat_template", None)
2338
+ if _chat_template_ is None: _chat_template_ = ""
2339
+ _supported_keys_ = set(("prompt", "chosen", "rejected", "completion", "messages", "label"))
2340
+
2341
+ prompts_text = []
2342
+ for _example_ in prompts:
2343
+ _tokenizer_kwargs_ = {}
2344
+ if type(_example_) is not dict:
2345
+ _example_ = {"prompt": _example_}
2346
+ _left_keys_ = _example_.keys() - _supported_keys_
2347
+ for k in _left_keys_:
2348
+ if k in _chat_template_:
2349
+ v = _example_[k]
2350
+ if type(v) is str:
2351
+ _tokenizer_kwargs_[k] = v
2352
+ _x_ = maybe_apply_chat_template(_example_, self.processing_class, **_tokenizer_kwargs_)["prompt"]
2353
+ prompts_text.append(_x_)
2354
+ if images is not None:
2355
+ prompt_inputs = self.processing_class(text=prompts_text, padding=True, return_tensors="pt", **kwargs)
2356
+ prompt_inputs = super()._prepare_inputs(prompt_inputs)
2357
+ forward_kwargs = {k: v for k, v in prompt_inputs.items() if k not in ["input_ids", "attention_mask"]}
2358
+ else:
2359
+ forward_kwargs = {}
2360
+
2361
+ # Generate completions using either vLLM or regular generation
2362
+ if self.use_vllm:
2363
+ if self.vllm_mode == "colocate" and self.args.vllm_enable_sleep_mode:
2364
+ # wake up colocated vLLM instances if needed
2365
+ torch.cuda.empty_cache() # required to avoid OOM in some cases
2366
+ self.llm.wake_up()
2367
+
2368
+ # First, update the vLLM weights if needed
2369
+ if self.state.global_step != self._last_loaded_step:
2370
+ self._move_model_to_vllm()
2371
+ self._last_loaded_step = self.state.global_step
2372
+
2373
+ # Generate completions using vLLM: gather all prompts and use them in a single call in the main process
2374
+ if self.vllm_mode == "server":
2375
+ all_prompts_text = gather_object(prompts_text)
2376
+ if images is not None:
2377
+ all_images = gather_object(images)
2378
+
2379
+ if self.accelerator.is_main_process:
2380
+ # Since 'prompts' contains 'num_generations' duplicates, we first take unique prompts, and generate
2381
+ # num_generations outputs for each one. This is faster than generating outputs for each duplicate
2382
+ # prompt individually.
2383
+ ordered_set_of_prompts = all_prompts_text[:: self.num_generations]
2384
+
2385
+ if images is not None:
2386
+ ordered_set_of_images = all_images[:: self.num_generations]
2387
+ else:
2388
+ ordered_set_of_images = None
2389
+
2390
+ with profiling_context(self, "vLLM.generate"):
2391
+ output = self.vllm_client.generate(
2392
+ prompts=ordered_set_of_prompts,
2393
+ images=ordered_set_of_images,
2394
+ n=self.num_generations,
2395
+ repetition_penalty=self.repetition_penalty,
2396
+ temperature=self.temperature,
2397
+ top_p=self.top_p,
2398
+ top_k=-1 if self.top_k is None else self.top_k,
2399
+ min_p=0.0 if self.min_p is None else self.min_p,
2400
+ max_tokens=self.max_completion_length,
2401
+ truncate_prompt_tokens=self.max_prompt_length,
2402
+ guided_decoding_regex=self.guided_decoding_regex,
2403
+ generation_kwargs=self.args.generation_kwargs,
2404
+ )
2405
+ payload = (output["prompt_ids"], output["completion_ids"], output["logprobs"])
2406
+ else:
2407
+ payload = None
2408
+
2409
+ # Broadcast the completions from the main process to all processes, ensuring each process receives its corresponding slice.
2410
+ obj_list = [payload]
2411
+ broadcast_object_list(obj_list, from_process=0)
2412
+ all_prompt_ids, all_completion_ids, all_logprobs = obj_list[0]
2413
+
2414
+ # At this point, we only get 1 copy of each prompt, so we need to repeat them num_generations times
2415
+ all_prompt_ids = [ids for ids in all_prompt_ids for _ in range(self.num_generations)]
2416
+
2417
+ process_slice = slice(
2418
+ self.accelerator.process_index * len(prompts),
2419
+ (self.accelerator.process_index + 1) * len(prompts),
2420
+ )
2421
+ prompt_ids = all_prompt_ids[process_slice]
2422
+ completion_ids = all_completion_ids[process_slice]
2423
+ logprobs = all_logprobs[process_slice]
2424
+
2425
+ # Generate completions using colocated vLLM instances: each device holds vLLM copy and work on their own batch of prompts
2426
+ elif self.vllm_mode == "colocate":
2427
+ if self.guided_decoding_regex:
2428
+ guided_decoding = GuidedDecodingParams(regex=self.guided_decoding_regex)
2429
+ else:
2430
+ guided_decoding = None
2431
+
2432
+ generation_kwargs = {
2433
+ "n": 1, # vLLM on each GPU generates only 1 in colocate mode
2434
+ "repetition_penalty": self.repetition_penalty,
2435
+ "temperature": self.temperature,
2436
+ "top_p": self.top_p,
2437
+ "top_k": -1 if self.top_k is None else self.top_k,
2438
+ "min_p": 0.0 if self.min_p is None else self.min_p,
2439
+ "max_tokens": self.max_completion_length,
2440
+ "truncate_prompt_tokens": self.max_prompt_length,
2441
+ "guided_decoding": guided_decoding,
2442
+ "logprobs": 0, # only return the logprob of the generated token
2443
+ }
2444
+ if self.args.generation_kwargs is not None:
2445
+ generation_kwargs.update(self.args.generation_kwargs)
2446
+ sampling_params = SamplingParams(**grpo_update_SamplingParams(SamplingParams, generation_kwargs, getattr(self.args, 'vllm_sampling_params', None)))
2447
+
2448
+ if self.vllm_tensor_parallel_size > 1:
2449
+ # Gather prompts from all ranks in the TP group and flatten.
2450
+ # Each rank starts with its own prompts; after gathering, all ranks see the full group set.
2451
+ orig_size = len(prompts_text)
2452
+ gathered_prompts = [None for _ in range(self.vllm_tensor_parallel_size)]
2453
+ torch.distributed.all_gather_object(gathered_prompts, prompts_text, group=self.tp_group)
2454
+ all_prompts_text = [p for sublist in gathered_prompts for p in sublist]
2455
+
2456
+ if images is not None:
2457
+ gathered_images = [None for _ in range(self.vllm_tensor_parallel_size)]
2458
+ torch.distributed.all_gather_object(gathered_images, images, group=self.tp_group)
2459
+ all_images = [img for sublist in gathered_images for img in sublist]
2460
+ else:
2461
+ all_images = None
2462
+ else:
2463
+ all_prompts_text = prompts_text
2464
+ all_images = images
2465
+
2466
+ if images is not None and all_images:
2467
+ vllm_inputs = []
2468
+ for prompt, image_list in zip(all_prompts_text, all_images):
2469
+ vllm_inputs.append({"prompt": prompt, "multi_modal_data": {"image": image_list}})
2470
+
2471
+ else:
2472
+ vllm_inputs = all_prompts_text
2473
+
2474
+ with profiling_context(self, "vLLM.generate"):
2475
+ all_outputs = self.llm.generate(vllm_inputs, sampling_params=sampling_params, use_tqdm=False, lora_request = self.model.load_lora('grpo_trainer_lora_model_' + (os.environ.get('CUDA_VISIBLE_DEVICES', '0').replace(',','')), load_tensors = True))
2476
+
2477
+ all_prompt_ids = [output.prompt_token_ids for output in all_outputs]
2478
+ all_completion_ids = [output.token_ids for outputs in all_outputs for output in outputs.outputs]
2479
+ all_logprobs = [
2480
+ [next(iter(lp.values())).logprob for lp in output.logprobs]
2481
+ for outputs in all_outputs
2482
+ for output in outputs.outputs
2483
+ ]
2484
+
2485
+ if self.vllm_tensor_parallel_size > 1:
2486
+ # Slice completions for this rank within its TP group.
2487
+ # Each rank generates all outputs — we keep only our share.
2488
+ local_rank_in_group = torch.distributed.get_rank(group=self.tp_group)
2489
+ tp_slice = slice(local_rank_in_group * orig_size, (local_rank_in_group + 1) * orig_size)
2490
+ prompt_ids = all_prompt_ids[tp_slice]
2491
+ completion_ids = all_completion_ids[tp_slice]
2492
+ logprobs = all_logprobs[tp_slice]
2493
+ else:
2494
+ prompt_ids = all_prompt_ids
2495
+ completion_ids = all_completion_ids
2496
+ logprobs = all_logprobs
2497
+
2498
+ if self.args.vllm_enable_sleep_mode:
2499
+ self.llm.sleep(level=1)
2500
+
2501
+ elif self.use_transformers_paged:
2502
+ # Re-process inputs for paged generation if needed
2503
+ # Note: images are already validated and preprocessed above
2504
+ paged_prompt_inputs = self.processing_class(text=prompts_text, **kwargs)
2505
+ previous_attn = self.model_wrapped.config._attn_implementation
2506
+
2507
+ if is_flash_attn_2_available():
2508
+ self.model_wrapped.config._attn_implementation = "paged_attention"
2509
+ else:
2510
+ self.model_wrapped.config._attn_implementation = "sdpa_paged"
2511
+ with (
2512
+ profiling_context(self, "transformers.generate_batch"),
2513
+ unwrap_model_for_generation(
2514
+ self.model_wrapped, self.accelerator, gather_deepspeed3_params=self.args.ds3_gather_for_generation
2515
+ ) as unwrapped_model,
2516
+ torch.no_grad(),
2517
+ FSDP.summon_full_params(self.model_wrapped, recurse=False) if self.is_fsdp_enabled else nullcontext(),
2518
+ ):
2519
+ # Cast to the appropriate dtype based on training configuration
2520
+ if self.args.bf16:
2521
+ unwrapped_model.to(torch.bfloat16)
2522
+ elif self.args.fp16:
2523
+ unwrapped_model.to(torch.float16)
2524
+ with torch.inference_mode():
2525
+ all_outputs = unwrapped_model.generate_batch(
2526
+ paged_prompt_inputs.input_ids, generation_config=self.generation_config, progress_bar=False
2527
+ )
2528
+ unwrapped_model.train() # restore training mode, as generate_batch forces eval mode
2529
+ completion_ids = [output.generated_tokens for output in all_outputs.values()]
2530
+ prompt_ids = paged_prompt_inputs.input_ids
2531
+ # Restore the original attention implementation, training mode
2532
+ self.model_wrapped.config._attn_implementation = previous_attn
2533
+ logprobs = None # not used in this case
2534
+
2535
+ else:
2536
+ # Regular generation path
2537
+ generate_inputs = self.processing_class(
2538
+ text=prompts_text,
2539
+ return_tensors="pt",
2540
+ padding=True,
2541
+ padding_side="left",
2542
+ max_length=self.max_prompt_length,
2543
+ truncation=True,
2544
+ add_special_tokens=False,
2545
+ **kwargs,
2546
+ )
2547
+ generate_inputs = super()._prepare_inputs(generate_inputs)
2548
+
2549
+ with (
2550
+ profiling_context(self, "transformers.generate"),
2551
+ unwrap_model_for_generation(
2552
+ self.model_wrapped, self.accelerator, gather_deepspeed3_params=self.args.ds3_gather_for_generation
2553
+ ) as unwrapped_model,
2554
+ torch.no_grad(),
2555
+ FSDP.summon_full_params(self.model_wrapped, recurse=False) if self.is_fsdp_enabled else nullcontext(),
2556
+ ):
2557
+ prompt_completion_ids = unwrapped_model.generate(
2558
+ **generate_inputs, generation_config=self.generation_config, disable_compile=True
2559
+ )
2560
+ # Compute prompt length and extract completion ids
2561
+ prompt_ids, prompt_mask = generate_inputs["input_ids"], generate_inputs["attention_mask"]
2562
+ prompt_length = prompt_ids.size(1)
2563
+ completion_ids = prompt_completion_ids[:, prompt_length:]
2564
+
2565
+ # Mask everything after the first EOS token
2566
+ is_eos = completion_ids == self.eos_token_id
2567
+ eos_idx = torch.full((is_eos.size(0),), is_eos.size(1), dtype=torch.long, device=device)
2568
+ eos_idx[is_eos.any(dim=1)] = is_eos.int().argmax(dim=1)[is_eos.any(dim=1)]
2569
+ sequence_indices = torch.arange(is_eos.size(1), device=device).expand(is_eos.size(0), -1)
2570
+ completion_mask = (sequence_indices <= eos_idx.unsqueeze(1)).int()
2571
+ prompt_ids = [p[m].tolist() for p, m in zip(prompt_ids, prompt_mask.bool())]
2572
+ completion_ids = [c[m].tolist() for c, m in zip(completion_ids, completion_mask.bool())]
2573
+ logprobs = None # not used in this case
2574
+
2575
+ return prompt_ids, completion_ids, logprobs, forward_kwargs
2576
+
2577
+ def _generate(self, prompts: list[str], images: Optional[list]):
2578
+ device = self.accelerator.device
2579
+ mode = "train" if self.model.training else "eval"
2580
+
2581
+ prompt_ids, completion_ids, logprobs, forward_kwargs = self._generate_single_turn(prompts, images)
2582
+
2583
+ # Get completion length per sequence, used for logging
2584
+ prompt_lengths = torch.tensor([len(ids) for ids in prompt_ids], device=device)
2585
+ completion_lengths = torch.tensor([len(ids) for ids in completion_ids], device=device)
2586
+ agg_prompt_lengths = self.accelerator.gather(prompt_lengths)
2587
+ agg_completion_lengths = self.accelerator.gather(completion_lengths)
2588
+ total_prompt_tokens = agg_prompt_lengths.sum()
2589
+ total_completion_tokens = agg_completion_lengths.sum() # = num_items_in_batch, required for the DAPO loss
2590
+
2591
+ # Log the metrics
2592
+ if mode == "train":
2593
+ self.state.num_input_tokens_seen += (total_prompt_tokens + total_completion_tokens).item()
2594
+ self._metrics[mode]["num_tokens"] = [self.state.num_input_tokens_seen]
2595
+
2596
+ # Log completion lengths, mean, min, max
2597
+ self._metrics[mode]["completions/mean_length"].append(agg_completion_lengths.float().mean().item())
2598
+ self._metrics[mode]["completions/min_length"].append(agg_completion_lengths.float().min().item())
2599
+ self._metrics[mode]["completions/max_length"].append(agg_completion_lengths.float().max().item())
2600
+
2601
+ # Identify sequences that terminated with EOS and log their lengths
2602
+ eos_and_pad = [self.eos_token_id, self.pad_token_id]
2603
+ is_truncated = torch.tensor([ids[-1] not in eos_and_pad for ids in completion_ids], device=device)
2604
+ agg_is_truncated = self.accelerator.gather(is_truncated)
2605
+ self._metrics[mode]["completions/clipped_ratio"].append(agg_is_truncated.float().mean().item())
2606
+ term_completion_lengths = agg_completion_lengths[~agg_is_truncated]
2607
+ if len(term_completion_lengths) == 0: # edge case where no terminated sequences are found
2608
+ term_completion_lengths = torch.zeros(1, device=device)
2609
+ self._metrics[mode]["completions/mean_terminated_length"].append(term_completion_lengths.float().mean().item())
2610
+ self._metrics[mode]["completions/min_terminated_length"].append(term_completion_lengths.float().min().item())
2611
+ self._metrics[mode]["completions/max_terminated_length"].append(term_completion_lengths.float().max().item())
2612
+
2613
+ return prompt_ids, completion_ids, total_completion_tokens, logprobs, forward_kwargs
2614
+
2615
+ def _generate_and_score_completions(
2616
+ self, inputs: list[dict[str, Union[torch.Tensor, Any]]]
2617
+ ) -> dict[str, Union[torch.Tensor, Any]]:
2618
+ device = self.accelerator.device
2619
+ mode = "train" if self.model.training else "eval"
2620
+
2621
+ prompts = [x["prompt"] for x in inputs]
2622
+
2623
+ if "images" in inputs[0]:
2624
+ images = [example.get("images") for example in inputs]
2625
+ elif "image" in inputs[0]:
2626
+ images = [[example.get("image")] if example.get("image") is not None else None for example in inputs]
2627
+ else:
2628
+ images = None
2629
+ # Transformers requires at least one image in the batch, otherwise it throws an error
2630
+ if images is not None and all(img_list == [] for img_list in images):
2631
+ images = None
2632
+
2633
+ (
2634
+ prompt_ids_list,
2635
+ completion_ids_list,
2636
+ num_items_in_batch,
2637
+ sampling_per_token_logps_list,
2638
+ forward_kwargs,
2639
+ ) = self._generate(prompts, images)
2640
+
2641
+ # Convert lists of token IDs to padded tensors
2642
+ prompt_ids = [torch.tensor(ids, device=device) for ids in prompt_ids_list]
2643
+ prompt_mask = [torch.ones_like(ids, dtype=torch.long) for ids in prompt_ids]
2644
+ prompt_ids = pad(prompt_ids, padding_value=self.pad_token_id, padding_side="left")
2645
+ prompt_mask = pad(prompt_mask, padding_value=0, padding_side="left")
2646
+ completion_ids = [torch.tensor(ids, device=device) for ids in completion_ids_list]
2647
+ completion_mask = [torch.ones_like(ids, dtype=torch.long) for ids in completion_ids]
2648
+ completion_ids = pad(completion_ids, padding_value=self.pad_token_id, padding_side="right")
2649
+ completion_mask = pad(completion_mask, padding_value=0, padding_side="right")
2650
+ if sampling_per_token_logps_list is not None:
2651
+ sampling_per_token_logps = [torch.tensor(logps, device=device) for logps in sampling_per_token_logps_list]
2652
+ sampling_per_token_logps = pad(sampling_per_token_logps, padding_value=0.0, padding_side="right")
2653
+ else:
2654
+ sampling_per_token_logps = None
2655
+
2656
+ # If mask_truncated_completions is enabled, zero out truncated completions in completion_mask
2657
+ if self.mask_truncated_completions:
2658
+ eos_and_pad = [self.eos_token_id, self.pad_token_id]
2659
+ is_truncated = torch.tensor([ids[-1] not in eos_and_pad for ids in completion_ids_list], device=device)
2660
+ completion_mask = completion_mask * (~is_truncated).unsqueeze(1).int()
2661
+
2662
+ # Concatenate prompt_mask with completion_mask for logit computation
2663
+ prompt_completion_ids = torch.cat([prompt_ids, completion_ids], dim=1) # (B, P+C)
2664
+ attention_mask = torch.cat([prompt_mask, completion_mask], dim=1) # (B, P+C)
2665
+ # If token_type_ids are used, extend them with zeros for the completion part
2666
+ if "token_type_ids" in forward_kwargs:
2667
+ token_type_ids = forward_kwargs["token_type_ids"]
2668
+ forward_kwargs["token_type_ids"] = torch.cat(
2669
+ [token_type_ids, token_type_ids.new_zeros(completion_ids.shape)], dim=1
2670
+ )
2671
+
2672
+ logits_to_keep = completion_ids.size(1) # we only need to compute the logits for the completion tokens
2673
+
2674
+ batch_size = self.args.per_device_train_batch_size if mode == "train" else self.args.per_device_eval_batch_size
2675
+ try:
2676
+ # TRL 0.23.1 and below path
2677
+ if not has_images:
2678
+ # Left pad prompt before calculation old and ref hidden states
2679
+ prompt_completion_ids = left_pack_padding(prompt_completion_ids, self.processing_class.pad_token_id)
2680
+ self.model.for_training()
2681
+ except:
2682
+ # TRL 0.24.0 and below path
2683
+ if images is None:
2684
+ # Left pad prompt before calculation old and ref hidden states
2685
+ prompt_completion_ids = left_pack_padding(prompt_completion_ids, self.processing_class.pad_token_id)
2686
+ self.model.for_training()
2687
+
2688
+ num_images = [len(img_list) for img_list in images] if images is not None else None
2689
+
2690
+ with torch.no_grad():
2691
+ # If the generation and optimization steps are misaligned—i.e., if generation does not occur at the end of
2692
+ # a full optimizer step (when gradient_accumulation_steps is not a multiple of generate_every)—then the
2693
+ # samples may come from an earlier version of the model. In that case, we need to track old_per_token_logps
2694
+ # for importance sampling. If the steps are aligned, importance sampling isn't necessary and we set
2695
+ # old_per_token_logps to None.
2696
+ # When using vLLM, we always compute old_per_token_logps for importance sampling, it was shown that the
2697
+ # distribution mismatch between vLLM and the training model can be large and harm the training.
2698
+ generate_every = self.args.steps_per_generation * self.num_iterations # generation frequency
2699
+
2700
+ if self.args.gradient_accumulation_steps % generate_every != 0 or (
2701
+ self.use_vllm
2702
+ ):
2703
+ old_per_token_logps, _ = self._get_per_token_logps_and_entropies(
2704
+ self.model,
2705
+ prompt_completion_ids,
2706
+ attention_mask,
2707
+ logits_to_keep,
2708
+ batch_size,
2709
+ num_images=num_images,
2710
+ **forward_kwargs, # may contain pixel_values, image_grid_thw, pixel_attention_mask and image_sizes
2711
+ )
2712
+ else:
2713
+ old_per_token_logps = None
2714
+
2715
+ # Compute the importance sampling ratio when using vLLM, to correct for potential distribution mismatch
2716
+ if self.use_vllm and self.vllm_importance_sampling_correction:
2717
+ importance_sampling_ratio = torch.exp(old_per_token_logps - sampling_per_token_logps)
2718
+ importance_sampling_ratio = torch.clamp(
2719
+ importance_sampling_ratio, max=self.vllm_importance_sampling_cap
2720
+ )
2721
+
2722
+ # Compute the per-token log probabilities for the reference model
2723
+ if self.beta != 0.0:
2724
+ if self.ref_model is not None:
2725
+ ref_per_token_logps, _ = self._get_per_token_logps_and_entropies(
2726
+ self.ref_model,
2727
+ prompt_completion_ids,
2728
+ attention_mask,
2729
+ logits_to_keep,
2730
+ batch_size=batch_size,
2731
+ num_images=num_images,
2732
+ **forward_kwargs, # may contain pixel_values, image_grid_thw, pixel_attention_mask and image_sizes
2733
+ )
2734
+ else:
2735
+ with self.accelerator.unwrap_model(self.model).disable_adapter():
2736
+ ref_per_token_logps, _ = self._get_per_token_logps_and_entropies(
2737
+ self.model,
2738
+ prompt_completion_ids,
2739
+ attention_mask,
2740
+ logits_to_keep,
2741
+ batch_size=batch_size,
2742
+ num_images=num_images,
2743
+ **forward_kwargs, # may contain pixel_values, image_grid_thw, pixel_attention_mask and image_sizes
2744
+ )
2745
+ else:
2746
+ ref_per_token_logps = None
2747
+
2748
+ # Decode
2749
+ prompts_text = self.processing_class.batch_decode(prompt_ids, skip_special_tokens=True)
2750
+ completions_text = self.processing_class.batch_decode(completion_ids, skip_special_tokens=True)
2751
+ if is_conversational(inputs[0]):
2752
+ completions = []
2753
+ for prompt, completion in zip(prompts, completions_text):
2754
+ bootstrap = prompt.pop()["content"] if prompt[-1]["role"] == "assistant" else ""
2755
+ completions.append([{"role": "assistant", "content": bootstrap + completion}])
2756
+ else:
2757
+ completions = completions_text
2758
+
2759
+ # Calculate rewards for each reward function. rewards_per_func aggregates rewards across all processes. This is
2760
+ # important because rewards will be normalized per group, and completions are distributed. We will later slice
2761
+ # rewards_per_func to extract each process's subset.
2762
+ rewards_per_func = self._calculate_rewards(inputs, prompts, completions, completion_ids_list)
2763
+
2764
+ # Apply weights to each reward function's output and sum
2765
+ rewards = (rewards_per_func * self.reward_weights.to(device).unsqueeze(0)).nansum(dim=1)
2766
+
2767
+ # Compute grouped-wise rewards
2768
+ mean_grouped_rewards = rewards.view(-1, self.num_generations).mean(dim=1)
2769
+
2770
+ # Normalize the rewards to compute the advantages
2771
+ mean_grouped_rewards = mean_grouped_rewards.repeat_interleave(self.num_generations, dim=0)
2772
+ advantages = rewards - mean_grouped_rewards
2773
+
2774
+ if self.scale_rewards in ["group", "none"]:
2775
+ # If self.scale_rewards = "none", we'll still log group level std
2776
+ std_rewards = rewards.view(-1, self.num_generations).std(dim=1)
2777
+ std_rewards = std_rewards.repeat_interleave(self.num_generations, dim=0)
2778
+ elif self.scale_rewards == "batch":
2779
+ # Compute global std
2780
+ std_rewards = rewards.std().expand_as(rewards)
2781
+ else:
2782
+ raise ValueError(
2783
+ f"Invalid value for scale_rewards: {self.scale_rewards}. Must be one of 'batch', 'group', or 'none'."
2784
+ )
2785
+
2786
+ is_std_zero = torch.isclose(std_rewards, torch.zeros_like(std_rewards))
2787
+ if self.scale_rewards != "none":
2788
+ advantages = advantages / (std_rewards + 1e-4)
2789
+
2790
+ # Slice to keep only the local part of the data
2791
+ process_slice = slice(
2792
+ self.accelerator.process_index * len(prompts),
2793
+ (self.accelerator.process_index + 1) * len(prompts),
2794
+ )
2795
+ all_process_advantages = advantages.clone() # keep the aggregated advantages for logging
2796
+ advantages = advantages[process_slice]
2797
+
2798
+ # Calculate mean reward per function, but only for samples where the function was applied (non-NaN values)
2799
+ for i, reward_func_name in enumerate(self.reward_func_names):
2800
+ mean_rewards = torch.nanmean(rewards_per_func[:, i]).item()
2801
+ self._metrics[mode][f"rewards/{reward_func_name}/mean"].append(mean_rewards)
2802
+ std_func_rewards = nanstd(rewards_per_func[:, i]).item()
2803
+ self._metrics[mode][f"rewards/{reward_func_name}/std"].append(std_func_rewards)
2804
+ self._metrics[mode]["reward"].append(mean_grouped_rewards.mean().item())
2805
+ self._metrics[mode]["reward_std"].append(std_rewards.mean().item())
2806
+ self._metrics[mode]["frac_reward_zero_std"].append(is_std_zero.float().mean().item())
2807
+
2808
+ # Log prompt and completion texts
2809
+ self._logs["prompt"].extend(gather_object(prompts_text))
2810
+ self._logs["completion"].extend(gather_object(completions_text))
2811
+ for i, name in enumerate(self.reward_func_names):
2812
+ self._logs["rewards"][name].extend(rewards_per_func[:, i].tolist())
2813
+ self._logs["advantages"].extend(all_process_advantages.tolist())
2814
+
2815
+ if images is not None:
2816
+ self._logs["images"].extend(gather_object(images))
2817
+
2818
+ if self.use_vllm and self.vllm_importance_sampling_correction:
2819
+ delta = torch.abs(old_per_token_logps - sampling_per_token_logps)
2820
+ delta = delta[completion_mask.bool()]
2821
+ mean_delta = torch.mean(delta) if delta.numel() > 0 else torch.tensor(0.0, device=device)
2822
+ max_delta = torch.max(delta) if delta.numel() > 0 else torch.tensor(0.0, device=device)
2823
+ self._metrics[mode]["sampling/sampling_logp_difference/mean"].append(
2824
+ self.accelerator.gather(mean_delta).mean().item()
2825
+ )
2826
+ self._metrics[mode]["sampling/sampling_logp_difference/max"].append(
2827
+ self.accelerator.gather(max_delta).max().item()
2828
+ )
2829
+
2830
+ flat_is_ratio = importance_sampling_ratio[completion_mask.bool()]
2831
+ min_importance_sampling_ratio = (
2832
+ torch.min(flat_is_ratio) if flat_is_ratio.numel() > 0 else torch.tensor(0.0, device=device)
2833
+ )
2834
+ mean_importance_sampling_ratio = (
2835
+ torch.mean(flat_is_ratio) if flat_is_ratio.numel() > 0 else torch.tensor(0.0, device=device)
2836
+ )
2837
+ max_importance_sampling_ratio = (
2838
+ torch.max(flat_is_ratio) if flat_is_ratio.numel() > 0 else torch.tensor(0.0, device=device)
2839
+ )
2840
+ self._metrics[mode]["sampling/importance_sampling_ratio/min"].append(
2841
+ nanmin(self.accelerator.gather(min_importance_sampling_ratio)).item()
2842
+ )
2843
+ self._metrics[mode]["sampling/importance_sampling_ratio/mean"].append(
2844
+ self.accelerator.gather(mean_importance_sampling_ratio).nanmean().item()
2845
+ )
2846
+ self._metrics[mode]["sampling/importance_sampling_ratio/max"].append(
2847
+ nanmax(self.accelerator.gather(max_importance_sampling_ratio)).item()
2848
+ )
2849
+
2850
+ output = {
2851
+ "prompt_ids": prompt_ids,
2852
+ "prompt_mask": prompt_mask,
2853
+ "completion_ids": completion_ids,
2854
+ "completion_mask": completion_mask,
2855
+ "advantages": advantages,
2856
+ "num_items_in_batch": num_items_in_batch,
2857
+ }
2858
+ if old_per_token_logps is not None:
2859
+ output["old_per_token_logps"] = old_per_token_logps
2860
+ if self.use_vllm and self.vllm_importance_sampling_correction:
2861
+ output["importance_sampling_ratio"] = importance_sampling_ratio
2862
+ if ref_per_token_logps is not None:
2863
+ output["ref_per_token_logps"] = ref_per_token_logps
2864
+ if "pixel_values" in forward_kwargs:
2865
+ output["pixel_values"] = forward_kwargs["pixel_values"]
2866
+ if "image_grid_thw" in forward_kwargs:
2867
+ output["image_grid_thw"] = forward_kwargs["image_grid_thw"]
2868
+ if "pixel_attention_mask" in forward_kwargs:
2869
+ output["pixel_attention_mask"] = forward_kwargs["pixel_attention_mask"]
2870
+ if "image_sizes" in forward_kwargs:
2871
+ output["image_sizes"] = forward_kwargs["image_sizes"]
2872
+ if "token_type_ids" in forward_kwargs:
2873
+ output["token_type_ids"] = forward_kwargs["token_type_ids"]
2874
+ if images is not None:
2875
+ output["num_images"] = num_images
2876
+ return output
2877
+
2878
+ def compute_liger_loss(self, unwrapped_model, inputs):
2879
+ # Compute the per-token log probabilities for the model
2880
+ prompt_ids, prompt_mask = inputs["prompt_ids"], inputs["prompt_mask"]
2881
+ completion_ids, completion_mask = inputs["completion_ids"], inputs["completion_mask"]
2882
+ input_ids = torch.cat([prompt_ids, completion_ids], dim=1)
2883
+ attention_mask = torch.cat([prompt_mask, completion_mask], dim=1)
2884
+ logits_to_keep = completion_ids.size(1) # we only need to compute the logits for the completion tokens
2885
+
2886
+ # Get the last hidden state of the model
2887
+ last_hidden_state = self._get_last_hidden_state(
2888
+ unwrapped_model,
2889
+ input_ids,
2890
+ attention_mask,
2891
+ logits_to_keep,
2892
+ inputs.get("pixel_values"),
2893
+ inputs.get("image_grid_thw"),
2894
+ inputs.get("pixel_attention_mask"),
2895
+ inputs.get("image_sizes"),
2896
+ )
2897
+
2898
+ # compute loss and metrics using liger grpo loss
2899
+ loss, metrics = self.liger_grpo_loss(
2900
+ _input=last_hidden_state,
2901
+ lin_weight=unwrapped_model.lm_head.weight,
2902
+ selected_token_ids=completion_ids,
2903
+ attention_mask=completion_mask,
2904
+ advantages=inputs["advantages"],
2905
+ bias=unwrapped_model.lm_head.bias,
2906
+ old_per_token_logps=inputs.get("old_per_token_logps"),
2907
+ ref_per_token_logps=inputs.get("ref_per_token_logps"),
2908
+ )
2909
+ # Extract metrics from the liger_grpo_loss output
2910
+ # KL divergence is the first metric when beta is non-zero
2911
+ mean_kl = metrics[0] if self.beta != 0.0 else None
2912
+ clip_ratio = metrics[-1]
2913
+
2914
+ mode = "train" if self.model.training else "eval"
2915
+ if self.beta != 0.0:
2916
+ self._metrics[mode]["kl"].append(self.accelerator.gather(mean_kl).mean().item())
2917
+ self._metrics[mode]["clip_ratio"].append(self.accelerator.gather(clip_ratio).mean().item())
2918
+ return loss / self.current_gradient_accumulation_steps
2919
+
2920
+ def compute_loss(
2921
+ self, model, inputs, return_outputs = False, num_items_in_batch = None
2922
+ ):
2923
+ if return_outputs:
2924
+ raise ValueError("The GRPOTrainer does not support returning outputs")
2925
+ # Compute the per-token log probabilities for the model
2926
+
2927
+ prompt_ids, prompt_mask = inputs["prompt_ids"], inputs["prompt_mask"]
2928
+ completion_ids, completion_mask = (
2929
+ inputs["completion_ids"],
2930
+ inputs["completion_mask"],
2931
+ )
2932
+ pixel_values, image_grid_thw = (
2933
+ inputs.get("pixel_values", None),
2934
+ inputs.get("image_grid_thw", None),
2935
+ )
2936
+ pixel_attention_mask, image_sizes = (
2937
+ inputs.get("pixel_attention_mask", None),
2938
+ inputs.get("image_sizes", None),
2939
+ )
2940
+ num_items_in_batch = inputs.get("num_items_in_batch", None)
2941
+ sampling_per_token_logps = inputs.get("sampling_per_token_logps", None)
2942
+ current_gradient_accumulation_steps = self.current_gradient_accumulation_steps
2943
+ num_processes = self.accelerator.num_processes
2944
+
2945
+ input_ids = torch.cat([prompt_ids, completion_ids], dim = 1)
2946
+ bsz, qlen = input_ids.shape
2947
+ attention_mask = torch.cat([prompt_mask, completion_mask], dim = 1)
2948
+ # attention_mask = None
2949
+ logits_to_keep = completion_ids.size(
2950
+ 1
2951
+ ) # we only need to compute the logits for the completion tokens
2952
+ _input_ids = input_ids
2953
+ _logits_to_keep = logits_to_keep
2954
+
2955
+ get_logps_func = (
2956
+ lambda model,
2957
+ input_ids,
2958
+ attention_mask,
2959
+ logits_to_keep,
2960
+ batch_size = None,
2961
+ compute_entropy = False,
2962
+ compute_efficient = False: self._get_per_token_logps(
2963
+ model, input_ids, attention_mask, logits_to_keep, compute_efficient
2964
+ )
2965
+ if hasattr(self, "_get_per_token_logps")
2966
+ else self._get_per_token_logps_and_entropies(
2967
+ model,
2968
+ input_ids,
2969
+ attention_mask,
2970
+ logits_to_keep,
2971
+ batch_size,
2972
+ compute_entropy,
2973
+ compute_efficient,
2974
+ )[0]
2975
+ ) # logps
2976
+
2977
+ per_token_logps = get_logps_func(
2978
+ model, input_ids, attention_mask, logits_to_keep, compute_efficient = True
2979
+ )
2980
+ # Compute the KL divergence between the model and the reference model
2981
+ # _prepare_inputs doesn't return reference log probs anymore. We need to calculate it ourselves.
2982
+ # https://github.com/huggingface/trl/blob/05bc43e960396581e458195b8388efe6b82cae1f/trl/trainer/grpo_trainer.py#L1328
2983
+ # if self.beta != 0.0:
2984
+ # with torch.inference_mode(), model.disable_adapter():
2985
+ # ref_per_token_logps = per_token_logps = get_logps_func(model, input_ids, attention_mask, logits_to_keep)
2986
+ # else:
2987
+ # ref_per_token_logps = None
2988
+ ref_hidden_states = inputs.get("ref_per_token_logps", None)
2989
+ # per_token_kl = torch.exp(ref_per_token_logps - per_token_logps) - (ref_per_token_logps - per_token_logps) - 1
2990
+ # x - x.detach() allows for preserving gradients from x
2991
+ advantages = inputs["advantages"]
2992
+ # per_token_loss = torch.exp(per_token_logps - per_token_logps.detach()) * advantages.unsqueeze(1)
2993
+ # per_token_loss = -(per_token_loss - self.beta * per_token_kl)
2994
+ # loss = ((per_token_loss * completion_mask).sum(dim=1) / completion_mask.sum(dim=1)).mean()
2995
+ old_hidden_states = inputs.get("old_per_token_logps", None)
2996
+
2997
+ input_ids = input_ids[:, -logits_to_keep:]
2998
+
2999
+ # Get logit softcapping and logit scale
3000
+ logit_softcapping = getattr(model.config, "final_logit_softcapping", 0) # Gemma
3001
+ if logit_softcapping is None:
3002
+ logit_softcapping = 0
3003
+ logit_scale_multiply = getattr(model.config, "logit_scale", 0) # Cohere
3004
+ if logit_scale_multiply is None:
3005
+ logit_scale_multiply = 0
3006
+ logit_scale_divide = getattr(model.config, "logits_scaling", 0) # Granite
3007
+ if logit_scale_divide is None:
3008
+ logit_scale_divide = 0
3009
+
3010
+ if per_token_logps is not None:
3011
+ if ref_hidden_states is not None:
3012
+ ref_hidden_states = ref_hidden_states[
3013
+ :, :-1, :
3014
+ ] # (B, L-1, V), exclude the last logit: it corresponds to the next token pred
3015
+ if old_hidden_states is not None:
3016
+ old_hidden_states = old_hidden_states[
3017
+ :, :-1, :
3018
+ ] # (B, L-1, V), exclude the last logit: it corresponds to the next token pred
3019
+ per_token_logps = per_token_logps[
3020
+ :, :-1, :
3021
+ ] # (B, L-1, V), exclude the last logit: it corresponds to the next token pred
3022
+
3023
+ loss, completion_length, mean_kl, delta, flat_is_ratio = (
3024
+ grpo_compute_loss_slow(
3025
+ ref_hidden_states,
3026
+ per_token_logps,
3027
+ old_hidden_states,
3028
+ input_ids,
3029
+ completion_mask,
3030
+ self.beta,
3031
+ advantages,
3032
+ pixel_values = pixel_values,
3033
+ image_grid_thw = image_grid_thw,
3034
+ loss_type = self.args.loss_type,
3035
+ importance_sampling_level = self.importance_sampling_level,
3036
+ epsilon_low = self.epsilon_low,
3037
+ epsilon_high = self.epsilon_high,
3038
+ max_completion_length = self.args.max_completion_length,
3039
+ delta = self.args.delta,
3040
+ temperature = self.args.temperature,
3041
+ logit_softcapping = logit_softcapping,
3042
+ logit_scale_multiply = logit_scale_multiply,
3043
+ logit_scale_divide = logit_scale_divide,
3044
+ num_items_in_batch = num_items_in_batch,
3045
+ current_gradient_accumulation_steps = current_gradient_accumulation_steps,
3046
+ num_processes = num_processes,
3047
+ sampling_per_token_logps = sampling_per_token_logps,
3048
+ )
3049
+ )
3050
+ else:
3051
+ if hasattr(self.args, "loss_type"):
3052
+ loss, completion_length, mean_kl, delta, flat_is_ratio = (
3053
+ grpo_accumulated_loss(
3054
+ trainer = self,
3055
+ input_ids = _input_ids,
3056
+ pixel_values = pixel_values,
3057
+ image_grid_thw = image_grid_thw,
3058
+ logits_to_keep = logits_to_keep,
3059
+ completion_mask = completion_mask,
3060
+ advantages = advantages,
3061
+ old_hidden_states = old_hidden_states,
3062
+ ref_hidden_states = ref_hidden_states,
3063
+ n_chunks = self.args.unsloth_num_chunks,
3064
+ loss_type = self.args.loss_type,
3065
+ importance_sampling_level = self.importance_sampling_level,
3066
+ epsilon_low = self.epsilon_low,
3067
+ epsilon_high = self.epsilon_high,
3068
+ max_completion_length = self.args.max_completion_length,
3069
+ delta = self.args.delta,
3070
+ temperature = self.args.temperature,
3071
+ logit_softcapping = logit_softcapping,
3072
+ logit_scale_multiply = logit_scale_multiply,
3073
+ logit_scale_divide = logit_scale_divide,
3074
+ attention_mask = attention_mask,
3075
+ num_items_in_batch = num_items_in_batch,
3076
+ current_gradient_accumulation_steps = current_gradient_accumulation_steps,
3077
+ num_processes = num_processes,
3078
+ sampling_per_token_logps = sampling_per_token_logps,
3079
+ )
3080
+ )
3081
+ else:
3082
+ # to ensure backwards compatibility with trl 0.15.2 and maybe even 0.17
3083
+ loss, completion_length, mean_kl = grpo_accumulated_loss(
3084
+ trainer = self,
3085
+ input_ids = _input_ids,
3086
+ logits_to_keep = logits_to_keep,
3087
+ completion_mask = completion_mask,
3088
+ advantages = advantages,
3089
+ old_hidden_states = old_hidden_states,
3090
+ ref_hidden_states = ref_hidden_states,
3091
+ n_chunks = self.args.unsloth_num_chunks,
3092
+ temperature = self.args.temperature,
3093
+ logit_softcapping = logit_softcapping,
3094
+ logit_scale_multiply = logit_scale_multiply,
3095
+ logit_scale_divide = logit_scale_divide,
3096
+ attention_mask = attention_mask,
3097
+ )
3098
+
3099
+ if "train" in self._metrics:
3100
+ mode = "eval" if self.control.should_evaluate else "train"
3101
+ self._metrics[mode]["completion_length"].append(completion_length.item())
3102
+ self._metrics[mode]["kl"].append(mean_kl.item())
3103
+ else:
3104
+ self._metrics["completion_length"].append(completion_length.item())
3105
+ self._metrics["kl"].append(mean_kl.item())
3106
+
3107
+ if self.use_vllm and delta is not None:
3108
+ mean_delta = (
3109
+ torch.mean(delta)
3110
+ if delta.numel() > 0
3111
+ else torch.tensor(0.0, device = self.model.device)
3112
+ )
3113
+ max_delta = (
3114
+ torch.max(delta)
3115
+ if delta.numel() > 0
3116
+ else torch.tensor(0.0, device = self.model.device)
3117
+ )
3118
+ self._metrics[mode]["sampling/sampling_logp_difference/mean"].append(
3119
+ self.accelerator.gather(mean_delta).mean().item()
3120
+ )
3121
+ self._metrics[mode]["sampling/sampling_logp_difference/max"].append(
3122
+ self.accelerator.gather(max_delta).max().item()
3123
+ )
3124
+
3125
+ min_importance_sampling_ratio = (
3126
+ torch.min(flat_is_ratio)
3127
+ if flat_is_ratio.numel() > 0
3128
+ else torch.tensor(0.0, device = self.model.device)
3129
+ )
3130
+ mean_importance_sampling_ratio = (
3131
+ torch.mean(flat_is_ratio)
3132
+ if flat_is_ratio.numel() > 0
3133
+ else torch.tensor(0.0, device = self.model.device)
3134
+ )
3135
+ max_importance_sampling_ratio = (
3136
+ torch.max(flat_is_ratio)
3137
+ if flat_is_ratio.numel() > 0
3138
+ else torch.tensor(0.0, device = self.model.device)
3139
+ )
3140
+ self._metrics[mode]["sampling/importance_sampling_ratio/min"].append(
3141
+ nanmin(self.accelerator.gather(min_importance_sampling_ratio)).item()
3142
+ )
3143
+ self._metrics[mode]["sampling/importance_sampling_ratio/mean"].append(
3144
+ self.accelerator.gather(mean_importance_sampling_ratio).nanmean().item()
3145
+ )
3146
+ self._metrics[mode]["sampling/importance_sampling_ratio/max"].append(
3147
+ nanmax(self.accelerator.gather(max_importance_sampling_ratio)).item()
3148
+ )
3149
+
3150
+ return loss
3151
+
3152
+ def _compute_loss(self, model, inputs):
3153
+ # Compute the per-token log probabilities for the model
3154
+ prompt_ids, prompt_mask = inputs["prompt_ids"], inputs["prompt_mask"]
3155
+ completion_ids, completion_mask = inputs["completion_ids"], inputs["completion_mask"]
3156
+ input_ids = torch.cat([prompt_ids, completion_ids], dim=1)
3157
+ attention_mask = torch.cat([prompt_mask, completion_mask], dim=1)
3158
+ logits_to_keep = completion_ids.size(1) # we only need to compute the logits for the completion tokens
3159
+
3160
+ # Compute the per_token_logps and the entropy at each position in the completion
3161
+ per_token_logps, entropies = self._get_per_token_logps_and_entropies(
3162
+ model,
3163
+ input_ids,
3164
+ attention_mask,
3165
+ logits_to_keep,
3166
+ compute_entropy=True,
3167
+ pixel_values=inputs.get("pixel_values"),
3168
+ image_grid_thw=inputs.get("image_grid_thw"),
3169
+ num_images=inputs.get("num_images"),
3170
+ pixel_attention_mask=inputs.get("pixel_attention_mask"),
3171
+ image_sizes=inputs.get("image_sizes"),
3172
+ token_type_ids=inputs.get("token_type_ids"),
3173
+ )
3174
+
3175
+ if self.top_entropy_quantile < 1.0:
3176
+ entropy_mask = self.get_high_entropy_mask(entropies, completion_mask, 1 - self.top_entropy_quantile)
3177
+ else:
3178
+ entropy_mask = None
3179
+
3180
+ # Compute the KL divergence between the model and the reference model
3181
+ if self.beta != 0.0:
3182
+ ref_per_token_logps = inputs["ref_per_token_logps"]
3183
+ per_token_kl = (
3184
+ torch.exp(ref_per_token_logps - per_token_logps) - (ref_per_token_logps - per_token_logps) - 1
3185
+ )
3186
+
3187
+ # Compute the loss
3188
+ advantages = inputs["advantages"]
3189
+ # When num_iterations == 1 and steps_per_generation <= gradient_accumulation_steps,
3190
+ # old_per_token_logps == per_token_logps. In this case we can skip its computation
3191
+ # (see _generate_and_score_completions) and instead use per_token_logps.detach().
3192
+ # The exception is when using vLLM, where we always compute old_per_token_logps
3193
+ # for importance sampling
3194
+ old_per_token_logps = inputs.get("old_per_token_logps")
3195
+ old_per_token_logps = per_token_logps.detach() if old_per_token_logps is None else old_per_token_logps
3196
+
3197
+ log_ratio = per_token_logps - old_per_token_logps
3198
+ if self.importance_sampling_level == "token":
3199
+ log_importance_weights = log_ratio
3200
+ elif self.importance_sampling_level == "sequence":
3201
+ log_importance_weights = (log_ratio * completion_mask).sum(-1) / completion_mask.sum(-1).clamp(min=1.0)
3202
+ log_importance_weights = log_importance_weights.unsqueeze(-1)
3203
+ else:
3204
+ raise ValueError(
3205
+ f"Unknown importance sampling level: {self.importance_sampling_level}. Possible values are 'token' "
3206
+ "and 'sequence'."
3207
+ )
3208
+ # From here, log_importance_weights (and all subsequent tensors, coef_1, coef_2, etc.) shape depends on
3209
+ # importance_sampling_level: "token" level: (B, T); "sequence" level: (B, 1)
3210
+
3211
+ coef_1 = torch.exp(log_importance_weights)
3212
+ coef_2 = torch.clamp(coef_1, 1 - self.epsilon_low, 1 + self.epsilon_high)
3213
+
3214
+ # Two-sided clipping
3215
+ if self.args.delta is not None:
3216
+ coef_1 = torch.clamp(coef_1, max=self.args.delta)
3217
+
3218
+ per_token_loss1 = coef_1 * advantages.unsqueeze(1)
3219
+ per_token_loss2 = coef_2 * advantages.unsqueeze(1)
3220
+ per_token_loss = -torch.min(per_token_loss1, per_token_loss2)
3221
+ if entropy_mask is not None:
3222
+ per_token_loss = per_token_loss * entropy_mask
3223
+
3224
+ if self.use_vllm and self.vllm_importance_sampling_correction:
3225
+ per_token_loss = per_token_loss * inputs["importance_sampling_ratio"]
3226
+
3227
+ if self.beta != 0.0:
3228
+ per_token_loss = per_token_loss + self.beta * per_token_kl
3229
+
3230
+ if self.loss_type == "grpo":
3231
+ loss = ((per_token_loss * completion_mask).sum(-1) / completion_mask.sum(-1).clamp(min=1.0)).mean()
3232
+ loss = loss / self.current_gradient_accumulation_steps
3233
+ elif self.loss_type == "bnpo":
3234
+ loss = (per_token_loss * completion_mask).sum() / completion_mask.sum().clamp(min=1.0)
3235
+ loss = loss / self.current_gradient_accumulation_steps
3236
+ elif self.loss_type == "dr_grpo":
3237
+ loss = (per_token_loss * completion_mask).sum() / (per_token_loss.size(0) * self.max_completion_length)
3238
+ loss = loss / self.current_gradient_accumulation_steps
3239
+ elif self.loss_type == "dapo":
3240
+ normalizer = inputs["num_items_in_batch"] / self.accelerator.num_processes
3241
+ loss = (per_token_loss * completion_mask).sum() / normalizer
3242
+ else:
3243
+ raise ValueError(f"Unknown loss type: {self.loss_type}")
3244
+
3245
+ # Log the metrics
3246
+ mode = "train" if self.model.training else "eval"
3247
+
3248
+ completion_token_count = completion_mask.sum().clamp(min=1.0)
3249
+
3250
+ def masked_batch_mean(x):
3251
+ if x.shape[1] == 1: # when importance_sampling_level == "sequence"
3252
+ return x.mean()
3253
+ else:
3254
+ return (x * completion_mask).sum() / completion_token_count
3255
+
3256
+ if self.beta != 0.0:
3257
+ mean_kl = masked_batch_mean(per_token_kl)
3258
+ self._metrics[mode]["kl"].append(self.accelerator.gather(mean_kl).nanmean().item())
3259
+
3260
+ mean_entropy = masked_batch_mean(entropies)
3261
+ self._metrics[mode]["entropy"].append(self.accelerator.gather(mean_entropy).nanmean().item())
3262
+
3263
+ # Compute the clipped probability ratios
3264
+ is_low_clipped = (coef_1 < 1 - self.epsilon_low) & (advantages.unsqueeze(1) < 0)
3265
+ is_high_clipped = (coef_1 > 1 + self.epsilon_high) & (advantages.unsqueeze(1) > 0)
3266
+ is_region_clipped = is_low_clipped | is_high_clipped
3267
+
3268
+ low_clip = masked_batch_mean(is_low_clipped.float())
3269
+ high_clip = masked_batch_mean(is_high_clipped.float())
3270
+ clip_ratio = masked_batch_mean(is_region_clipped.float())
3271
+
3272
+ gathered_low_clip = self.accelerator.gather(low_clip)
3273
+ self._metrics[mode]["clip_ratio/low_mean"].append(gathered_low_clip.nanmean().item())
3274
+ self._metrics[mode]["clip_ratio/low_min"].append(nanmin(gathered_low_clip).item())
3275
+ gathered_high_clip = self.accelerator.gather(high_clip)
3276
+ self._metrics[mode]["clip_ratio/high_mean"].append(gathered_high_clip.nanmean().item())
3277
+ self._metrics[mode]["clip_ratio/high_max"].append(nanmax(gathered_high_clip).item())
3278
+ gathered_clip_ratio = self.accelerator.gather(clip_ratio)
3279
+ self._metrics[mode]["clip_ratio/region_mean"].append(gathered_clip_ratio.nanmean().item())
3280
+ return loss
3281
+
3282
+ def prediction_step(self, model, inputs, prediction_loss_only, ignore_keys: Optional[list[str]] = None):
3283
+ inputs = self._prepare_inputs(inputs)
3284
+ with torch.no_grad():
3285
+ with self.compute_loss_context_manager():
3286
+ loss = self.compute_loss(model, inputs)
3287
+ loss = loss.mean().detach()
3288
+ return loss, None, None
3289
+
3290
+ def log(self, logs: dict[str, float], start_time: Optional[float] = None) -> None:
3291
+ mode = "train" if self.model.training else "eval"
3292
+ metrics = {key: sum(val) / len(val) for key, val in self._metrics[mode].items()} # average the metrics
3293
+
3294
+ # This method can be called both in training and evaluation. When called in evaluation, the keys in `logs`
3295
+ # start with "eval_". We need to add the prefix "eval_" to the keys in `metrics` to match the format.
3296
+ if mode == "eval":
3297
+ metrics = {f"eval_{key}": val for key, val in metrics.items()}
3298
+
3299
+ logs = {**logs, **metrics}
3300
+ super().log(logs, start_time)
3301
+ self._metrics[mode].clear()
3302
+
3303
+ if self.accelerator.is_main_process and self.log_completions:
3304
+ if is_rich_available():
3305
+ print_prompt_completions_sample(
3306
+ self._logs["prompt"],
3307
+ self._logs["completion"],
3308
+ self._logs["rewards"],
3309
+ self._logs["advantages"],
3310
+ self.state.global_step,
3311
+ self.num_completions_to_print,
3312
+ )
3313
+
3314
+ if self.args.report_to and "wandb" in self.args.report_to and wandb.run is not None:
3315
+ import pandas as pd
3316
+
3317
+ table = {
3318
+ "step": [str(self.state.global_step)] * len(self._logs["prompt"]),
3319
+ "prompt": self._logs["prompt"],
3320
+ "completion": self._logs["completion"],
3321
+ **self._logs["rewards"],
3322
+ "advantage": self._logs["advantages"],
3323
+ }
3324
+
3325
+ if self._logs["images"]:
3326
+ table["images"] = []
3327
+ for image_list in self._logs["images"]:
3328
+ # Convert images to wandb Image objects for proper visualization
3329
+ table["images"].append([wandb.Image(image) for image in image_list])
3330
+
3331
+ df = pd.DataFrame(table)
3332
+ if self.wandb_log_unique_prompts:
3333
+ df = df.drop_duplicates(subset=["prompt"])
3334
+ wandb.log({"completions": wandb.Table(dataframe=df)})
3335
+
3336
+ # Ensure the model card is saved along with the checkpoint
3337
+ def _save_checkpoint(self, model, trial):
3338
+ if self.args.hub_model_id is None:
3339
+ model_name = Path(self.args.output_dir).name
3340
+ else:
3341
+ model_name = self.args.hub_model_id.split("/")[-1]
3342
+ self.create_model_card(model_name=model_name)
3343
+ super()._save_checkpoint(model, trial)
3344
+ class UnslothGRPOTrainer(_UnslothGRPOTrainer):
3345
+ """
3346
+
3347
+ Trainer for the Group Relative Policy Optimization (GRPO) method. This algorithm was initially proposed in the
3348
+ paper [DeepSeekMath: Pushing the Limits of Mathematical Reasoning in Open Language
3349
+ Models](https://huggingface.co/papers/2402.03300).
3350
+
3351
+ Example:
3352
+
3353
+ ```python
3354
+ from datasets import load_dataset
3355
+ from trl import GRPOTrainer
3356
+
3357
+ dataset = load_dataset("trl-lib/tldr", split="train")
3358
+ def reward_func(completions, **kwargs):
3359
+ # Dummy reward function that rewards completions with more unique letters.
3360
+ return [float(len(set(completion))) for completion in completions]
3361
+ trainer = GRPOTrainer(
3362
+ model="Qwen/Qwen2-0.5B-Instruct",
3363
+ reward_funcs=reward_func,
3364
+ train_dataset=dataset,
3365
+ )
3366
+
3367
+ trainer.train()
3368
+ ```
3369
+
3370
+ Args:
3371
+ model (`Union[str, PreTrainedModel]`):
3372
+ Model to be trained. Can be either:
3373
+
3374
+ - A string, being the *model id* of a pretrained model hosted inside a model repo on huggingface.co, or a
3375
+ path to a *directory* containing model weights saved using
3376
+ [`~transformers.PreTrainedModel.save_pretrained`], e.g., `'./my_model_directory/'`. The model is loaded
3377
+ using [`~transformers.AutoModelForCausalLM.from_pretrained`] with the keyword arguments in
3378
+ `args.model_init_kwargs`.
3379
+ - A [`~transformers.PreTrainedModel`] object. Only causal language models are supported.
3380
+ reward_funcs (`Union[RewardFunc, list[RewardFunc]]`):
3381
+ Reward functions to be used for computing the rewards. To compute the rewards, we call all the reward
3382
+ functions with the prompts and completions and sum the rewards. Can be either:
3383
+
3384
+ - A single reward function, such as:
3385
+ - A string: The *model ID* of a pretrained model hosted inside a model repo on huggingface.co, or a
3386
+ path to a *directory* containing model weights saved using
3387
+ [`~transformers.PreTrainedModel.save_pretrained`], e.g., `'./my_model_directory/'`. The model is loaded
3388
+ using [`~transformers.AutoModelForSequenceClassification.from_pretrained`] with `num_labels=1` and the
3389
+ keyword arguments in `args.model_init_kwargs`.
3390
+ - A [`~transformers.PreTrainedModel`] object: Only sequence classification models are supported.
3391
+ - A custom reward function: The function is provided with the prompts and the generated completions,
3392
+ plus any additional columns in the dataset. It should return a list of rewards. Custom reward
3393
+ functions can also return `None` when the reward is not applicable to those samples. This is useful
3394
+ for multi-task training where different reward functions apply to different types of samples. When a
3395
+ reward function returns `None` for a sample, that reward function is excluded from the reward
3396
+ calculation for that sample. For more details, see [Using a custom reward
3397
+ function](#using-a-custom-reward-function).
3398
+
3399
+ The trainer's state is also passed to the reward function. The trainer's state is an instance of
3400
+ [`~transformers.TrainerState`] and can be accessed by accessing the `trainer_state` argument to the
3401
+ reward function's signature.
3402
+ - A list of reward functions, where each item can independently be any of the above types. Mixing different
3403
+ types within the list (e.g., a string model ID and a custom reward function) is allowed.
3404
+ args ([`GRPOConfig`], *optional*):
3405
+ Configuration for this trainer. If `None`, a default configuration is used.
3406
+ train_dataset ([`~datasets.Dataset`] or [`~datasets.IterableDataset`]):
3407
+ Dataset to use for training. It must include a column `"prompt"`. Any additional columns in the dataset is
3408
+ ignored. The format of the samples can be either:
3409
+
3410
+ - [Standard](dataset_formats#standard): Each sample contains plain text.
3411
+ - [Conversational](dataset_formats#conversational): Each sample contains structured messages (e.g., role
3412
+ and content).
3413
+ eval_dataset ([`~datasets.Dataset`], [`~datasets.IterableDataset`] or `dict[str, Union[Dataset, IterableDataset]]`):
3414
+ Dataset to use for evaluation. It must meet the same requirements as `train_dataset`.
3415
+ processing_class ([`~transformers.PreTrainedTokenizerBase`], [`~transformers.ProcessorMixin`], *optional*):
3416
+ Processing class used to process the data. The padding side must be set to "left". If `None`, the
3417
+ processing class is loaded from the model's name with [`~transformers.AutoProcessor.from_pretrained`]. A
3418
+ padding token, `tokenizer.pad_token`, must be set. If the processing class has not set a padding token,
3419
+ `tokenizer.eos_token` will be used as the default.
3420
+ reward_processing_classes ([`~transformers.PreTrainedTokenizerBase`] or `list[PreTrainedTokenizerBase]`, *optional*):
3421
+ Processing classes corresponding to the reward functions specified in `reward_funcs`. Can be either:
3422
+
3423
+ - A single processing class: Used when `reward_funcs` contains only one reward function.
3424
+ - A list of processing classes: Must match the order and length of the reward functions in `reward_funcs`.
3425
+ If set to `None`, or if an element of the list corresponding to a [`~transformers.PreTrainedModel`] is
3426
+ `None`, the tokenizer for the model is automatically loaded using
3427
+ [`~transformers.AutoTokenizer.from_pretrained`]. For elements in `reward_funcs` that are custom reward
3428
+ functions (not [`~transformers.PreTrainedModel`]), the corresponding entries in `reward_processing_classes`
3429
+ are ignored.
3430
+ callbacks (list of [`~transformers.TrainerCallback`], *optional*):
3431
+ List of callbacks to customize the training loop. Will add those to the list of default callbacks detailed
3432
+ in [here](https://huggingface.co/docs/transformers/main_classes/callback).
3433
+
3434
+ If you want to remove one of the default callbacks used, use the [`~transformers.Trainer.remove_callback`]
3435
+ method.
3436
+ optimizers (`tuple[torch.optim.Optimizer, torch.optim.lr_scheduler.LambdaLR]`, *optional*, defaults to `(None, None)`):
3437
+ A tuple containing the optimizer and the scheduler to use. Will default to an instance of [`AdamW`] on your
3438
+ model and a scheduler given by [`get_linear_schedule_with_warmup`] controlled by `args`.
3439
+ peft_config ([`~peft.PeftConfig`], *optional*):
3440
+ PEFT configuration used to wrap the model. If `None`, the model is not wrapped.
3441
+
3442
+ """
3443
+ def __init__(
3444
+ self,
3445
+ model,
3446
+ reward_funcs,
3447
+ args = None,
3448
+ train_dataset = None,
3449
+ eval_dataset = None,
3450
+ processing_class = None,
3451
+ reward_processing_classes = None,
3452
+ callbacks = None,
3453
+ peft_config = None,
3454
+ **kwargs
3455
+ ):
3456
+ if args is None: args = UnslothGRPOConfig()
3457
+ use_bf16 = getattr(args, 'bf16', False)
3458
+ if type(use_bf16) is not bool: use_bf16 = False
3459
+ use_fp16 = getattr(args, 'fp16', False)
3460
+ if type(use_fp16) is not bool: use_fp16 = False
3461
+ force_float32 = False
3462
+ full_finetuning = os.environ.get('UNSLOTH_ENABLE_FULL_FINETUNING', '0') == '1'
3463
+ if not full_finetuning and (os.environ.get('UNSLOTH_FORCE_FLOAT32', '0') == '1'):
3464
+ print('Unsloth: Switching to float32 training since model cannot work with float16')
3465
+ force_float32 = True
3466
+ mixed_precision_dtype = os.environ.get('UNSLOTH_MIXED_PRECISION', 'float32')
3467
+ dtype = getattr(model.config, 'dtype', None) or getattr(model.config, 'torch_dtype', None)
3468
+ if dtype is None: dtype = model.get_input_embeddings().weight.dtype
3469
+ from unsloth_zoo.utils import _get_dtype
3470
+ dtype = _get_dtype(dtype)
3471
+ float16 = dtype == torch.float16
3472
+ if not force_float32 and (float16 and use_bf16): raise TypeError('Unsloth: Model is in float16 precision but you want to use bfloat16 precision. Set fp16 to `True` and bf16 to `False`')
3473
+ if not force_float32 and (not float16 and use_fp16): raise TypeError('Unsloth: Model is in bfloat16 precision but you want to use float16 precision. Set fp16 to `False` and bf16 to `True`')
3474
+ if force_float32:
3475
+ # Forced float32 training
3476
+ args.fp16 = False
3477
+ args.bf16 = False
3478
+ os.environ['ACCELERATE_MIXED_PRECISION'] = 'no'
3479
+ if hasattr(args, 'mixed_precision'): args.mixed_precision = 'no'
3480
+ # args.mixed_precision is a new argument which needs to be set now
3481
+ elif (not use_bf16 and not use_fp16) and mixed_precision_dtype == 'float32':
3482
+ # Mixed precision training
3483
+ args.fp16 = float16
3484
+ args.bf16 = not float16
3485
+ os.environ['ACCELERATE_MIXED_PRECISION'] = 'fp16' if float16 else 'bf16'
3486
+ if hasattr(args, 'mixed_precision'): args.mixed_precision = 'fp16' if float16 else 'bf16'
3487
+ # args.mixed_precision is a new argument which needs to be set now
3488
+ elif mixed_precision_dtype == 'bfloat16':
3489
+ # Both False since bfloat16 full finetuning doesn't do any autocasting.
3490
+ args.fp16 = False
3491
+ args.bf16 = False
3492
+ os.environ['ACCELERATE_MIXED_PRECISION'] = 'no'
3493
+ if hasattr(args, 'mixed_precision'): args.mixed_precision = 'no'
3494
+ # args.mixed_precision is a new argument which needs to be set now
3495
+
3496
+ if getattr(args, 'eval_dataset', None) is not None and getattr(args, 'eval_strategy', 'no') == 'no':
3497
+ args.eval_strategy = 'steps'
3498
+ if getattr(args, 'eval_steps', None) is None: args.eval_steps = 0.1
3499
+ ga_steps = getattr(args, 'gradient_accumulation_steps', None)
3500
+ if ga_steps is not None and ga_steps > 1:
3501
+ from transformers import __version__ as transformers_version
3502
+ if Version(transformers_version) <= Version('4.45.2'):
3503
+ print('**** Unsloth: Please use our fixed gradient_accumulation_steps by updating transformers, TRL and Unsloth!\n'
3504
+ '`pip install --upgrade --no-cache-dir --force-reinstall --no-deps unsloth transformers trl unsloth_zoo`')
3505
+ if getattr(args, 'eval_strategy', 'no') != 'no':
3506
+ eval_bsz = getattr(args, 'per_device_eval_batch_size', 8)
3507
+ if eval_bsz == 8 and args.per_device_train_batch_size < eval_bsz: args.per_device_eval_batch_size = args.per_device_train_batch_size
3508
+ if getattr(args, 'eval_accumulation_steps', None) is None and ga_steps is not None: args.eval_accumulation_steps = ga_steps
3509
+ fp16_full_eval = getattr(args, 'fp16_full_eval', False)
3510
+ if type(fp16_full_eval) is not bool: fp16_full_eval = False
3511
+ bf16_full_eval = getattr(args, 'bf16_full_eval', False)
3512
+ if type(bf16_full_eval) is not bool: bf16_full_eval = False
3513
+ if args.fp16 and bf16_full_eval: args.bf16_full_eval = False; args.fp16_full_eval = True
3514
+ if args.bf16 and fp16_full_eval: args.bf16_full_eval = True; args.fp16_full_eval = False
3515
+ if force_float32:
3516
+ args.bf16_full_eval = False
3517
+ args.fp16_full_eval = False
3518
+ elif os.environ.get('UNSLOTH_MIXED_PRECISION', 'float32') == 'bfloat16':
3519
+ args.bf16_full_eval = True
3520
+ args.fp16_full_eval = False
3521
+ elif not bf16_full_eval and not fp16_full_eval:
3522
+ args.bf16_full_eval = args.bf16
3523
+ args.fp16_full_eval = args.fp16
3524
+ _output_logits = False
3525
+ if locals().get('compute_metrics', None) is not None: _output_logits = True
3526
+ if locals().get('preprocess_logits_for_metrics', None) is not None: _output_logits = True
3527
+ if _output_logits:
3528
+ os.environ['UNSLOTH_RETURN_LOGITS'] = '1'
3529
+ if 'max_seq_length' not in locals() and not hasattr(args, 'max_seq_length'):
3530
+ pass
3531
+ else:
3532
+ model_max_seq_length = getattr(model, 'max_seq_length', None)
3533
+ args_max_seq_length = getattr(args, 'max_seq_length', None)
3534
+ if args_max_seq_length is None and model_max_seq_length is not None:
3535
+ max_seq_length = model.max_seq_length
3536
+ if hasattr(args, 'max_seq_length'): args.max_seq_length = max_seq_length
3537
+ if model is not None and hasattr(model, 'for_training'):
3538
+ model.for_training(use_gradient_checkpointing=getattr(args, 'gradient_checkpointing', True))
3539
+ if 'tokenizer' in locals() and hasattr(tokenizer, 'padding_side'): tokenizer.padding_side = 'right'
3540
+ if 'processing_class' in locals():
3541
+ if hasattr(processing_class, 'padding_side'): processing_class.padding_side = 'right'
3542
+ if hasattr(processing_class, 'tokenizer') and hasattr(processing_class.tokenizer, 'padding_side'): processing_class.tokenizer.padding_side = 'right'
3543
+ other_metrics = []
3544
+ if not isinstance(reward_funcs, list): _reward_funcs = [reward_funcs]
3545
+ else: _reward_funcs = reward_funcs
3546
+ for reward_func in _reward_funcs:
3547
+ try:
3548
+ reward_func_name = reward_func.__name__
3549
+ if True:
3550
+ other_metrics.append(f'rewards/{reward_func_name}/mean')
3551
+ if True:
3552
+ other_metrics.append(f'rewards/{reward_func_name}/std')
3553
+ if False:
3554
+ other_metrics.append(f'rewards/{reward_func_name}')
3555
+ except: pass
3556
+
3557
+ from unsloth_zoo.logging_utils import PatchRLStatistics
3558
+ PatchRLStatistics('grpo_trainer', other_metrics)
3559
+
3560
+ # [TODO] Fix up DataParallel multiplying batch sizes
3561
+ # [TODO] DDP works, but DP seems to not work? [TODO]
3562
+ if getattr(args, "parallel_mode", None) == ParallelMode.NOT_DISTRIBUTED and args.n_gpu > 1:
3563
+ if getattr(args, "_n_gpu", 1) != 1:
3564
+ args._n_gpu = 1
3565
+ if "model" in locals() and hasattr(model, "for_training"):
3566
+ model.for_training(use_gradient_checkpointing=getattr(args, 'gradient_checkpointing', True))
3567
+ super().__init__(
3568
+ model = model,
3569
+ reward_funcs = reward_funcs,
3570
+ args = args,
3571
+ train_dataset = train_dataset,
3572
+ eval_dataset = eval_dataset,
3573
+ processing_class = processing_class,
3574
+ reward_processing_classes = reward_processing_classes,
3575
+ callbacks = callbacks,
3576
+ peft_config = peft_config,**kwargs)
3577
+ if "model" in locals() and hasattr(model, "for_inference"):
3578
+ model.for_inference()
3579
+ if hasattr(self, 'neftune_hook_handle'):
3580
+ self.neftune_hook_handle.remove()
3581
+ if hasattr(self, 'neftune_hook_handle'): del self.neftune_hook_handle
3582
+ if getattr(args, 'neftune_noise_alpha', None) is not None:
3583
+ model.get_input_embeddings().neftune_noise_alpha = self.neftune_noise_alpha
3584
+ pass
3585
+ if hasattr(self, 'accelerator'):
3586
+ scaler = self.accelerator.scaler
3587
+ current_model = model
3588
+ while hasattr(current_model, 'model'):
3589
+ current_model.accelerator_scaler = scaler
3590
+ current_model = current_model.model
3591
+ current_model.accelerator_scaler = scaler
3592
+ pass
3593
+ if hasattr(self, 'train'):
3594
+ self.train = MethodType(prepare_for_training_mode(self.__class__.train), self)
3595
+ pass
3596
+
3597
+ pass
3598
+
3599
+
3600
+ if hasattr(logger, "addFilter"):
3601
+ import logging
3602
+ class HideLoggingMessage(logging.Filter):
3603
+ def __init__(self, text): self.text = text
3604
+ def filter(self, x): return not (self.text in x.getMessage())
3605
+ pass
3606
+ logger.addFilter(HideLoggingMessage("`use_cache=True`"))
3607
+