@@ -74,7 +74,80 @@ class GRPOBatch:
7474 rewards : torch .Tensor
7575
7676
77- def clip_outputs_after_think_token (rollouts , tokenizer , think_tokens , num_tokens ):
77+ # def clip_outputs_after_think_token(rollouts, tokenizer, num_tokens):
78+ # """
79+ # Clip token_ids and logprobs to keep only num_tokens after the </think> token sequence ends.
80+ # If </think> is not found, clip to just the first num_tokens.
81+ # Recompute the text from clipped tokens.
82+
83+ # Args:
84+ # rollouts: List of rollout objects
85+ # tokenizer: Tokenizer instance
86+ # think_tokens: List of token IDs for </think>
87+ # num_tokens: Number of tokens to keep after </think> token sequence ends (or from start if no </think>)
88+
89+ # Returns:
90+ # List of modified rollout objects
91+ # """
92+ # think_tokens = tokenizer.encode("</think>", add_special_tokens=False)
93+ # ret = []
94+ # for rollout in rollouts:
95+ # clipped_outputs = []
96+
97+ # for output in rollout.outputs:
98+ # # Find the position where </think> tokens start
99+ # think_token_len = len(think_tokens)
100+ # clip_index = None
101+
102+ # # Search for the think tokens sequence in token_ids
103+ # for i in range(len(output.token_ids) - think_token_len + 1):
104+ # if output.token_ids[i : i + think_token_len] == think_tokens:
105+ # # Clip to include everything up to and including </think> plus num_tokens after
106+ # clip_index = i + think_token_len + num_tokens
107+ # break
108+
109+ # # If </think> not found, clip to just the first num_tokens
110+ # if clip_index is None:
111+ # clip_index = num_tokens
112+
113+ # # Clip token_ids and logprobs
114+ # clipped_token_ids = output.token_ids[:clip_index]
115+ # clipped_logprobs = output.logprobs[:clip_index]
116+
117+ # # Recompute text from clipped tokens
118+ # clipped_text = tokenizer.decode(clipped_token_ids)
119+
120+ # # Recalculate cumulative_logprob from clipped logprobs
121+ # cumulative_logprob = 0.0
122+ # for logprob_dict in clipped_logprobs:
123+ # # Get the first token's logprob (the selected token)
124+ # first_token_id = list(logprob_dict.keys())[0]
125+ # cumulative_logprob += logprob_dict[first_token_id].logprob
126+
127+ # # Create new CompletionOutput with clipped data
128+ # clipped_output = type(output)(
129+ # index=output.index,
130+ # text=clipped_text,
131+ # token_ids=clipped_token_ids,
132+ # cumulative_logprob=cumulative_logprob,
133+ # logprobs=clipped_logprobs,
134+ # finish_reason=output.finish_reason,
135+ # stop_reason=output.stop_reason,
136+ # )
137+ # clipped_outputs.append(clipped_output)
138+
139+ # # Create new rollout object with clipped outputs
140+ # clipped_rollout = type(rollout)(
141+ # outputs=clipped_outputs,
142+ # # Copy other attributes from original rollout
143+ # **{k: v for k, v in vars(rollout).items() if k != "outputs"},
144+ # )
145+ # ret.append(clipped_rollout)
146+
147+ # return ret
148+
149+
150+ def clip_outputs_after_think_token (rollouts , tokenizer , num_tokens ):
78151 """
79152 Clip token_ids and logprobs to keep only num_tokens after the </think> token sequence ends.
80153 If </think> is not found, clip to just the first num_tokens.
@@ -94,16 +167,24 @@ def clip_outputs_after_think_token(rollouts, tokenizer, think_tokens, num_tokens
94167 clipped_outputs = []
95168
96169 for output in rollout .outputs :
97- # Find the position where </think> tokens start
98- think_token_len = len (think_tokens )
99170 clip_index = None
100171
101- # Search for the think tokens sequence in token_ids
102- for i in range (len (output .token_ids ) - think_token_len + 1 ):
103- if output .token_ids [i : i + think_token_len ] == think_tokens :
104- # Clip to include everything up to and including </think> plus num_tokens after
105- clip_index = i + think_token_len + num_tokens
106- break
172+ # Search for "</think>" in the decoded text to avoid tokenizer context issues
173+ text = output .text
174+ think_end_pos = text .find ("</think>" )
175+
176+ if think_end_pos != - 1 :
177+ # Found "</think>" in text - now find the corresponding token position
178+ # The end of "</think>" in the text
179+ think_end_pos += len ("</think>" )
180+
181+ # Decode tokens incrementally to find where we pass the think_end_pos
182+ for i in range (1 , len (output .token_ids ) + 1 ):
183+ decoded_text = tokenizer .decode (output .token_ids [:i ])
184+ if len (decoded_text ) >= think_end_pos :
185+ # We've passed the end of "</think>", now add num_tokens more
186+ clip_index = min (i + num_tokens , len (output .token_ids ))
187+ break
107188
108189 # If </think> not found, clip to just the first num_tokens
109190 if clip_index is None :
@@ -370,13 +451,9 @@ def __call__(
370451 ]
371452 for text in prompt_batch .meta_info .get ("suffix" )
372453 ]
373- think_tokens = self ._rollout_tokenizer .encode (
374- "</think>" , add_special_tokens = False
375- )
376454 rollouts = clip_outputs_after_think_token (
377455 rollouts ,
378456 self ._rollout_tokenizer ,
379- think_tokens ,
380457 self ._config .clip_rollout_after_think ,
381458 )
382459 if self ._config .loss_config .log_rollouts :
0 commit comments