@@ -61,6 +61,7 @@ def prepare_logits_processor(
6161 return processor_list
6262
6363
64+
6465@torch .inference_mode ()
6566def generate_stream (
6667 model ,
@@ -107,8 +108,6 @@ def generate_stream(
107108 input_echo_len = len (input_ids )
108109
109110 if model .config .is_encoder_decoder :
110- if logprobs is not None : # FIXME: Support logprobs for encoder-decoder models.
111- raise NotImplementedError
112111 encoder_output = model .encoder (
113112 input_ids = torch .as_tensor ([input_ids ], device = device )
114113 )[0 ]
@@ -117,6 +116,8 @@ def generate_stream(
117116 dtype = torch .int64 ,
118117 device = device ,
119118 )
119+ # For encoder-decoder models, we track decoder output tokens for logprobs
120+ # (This variable is not used directly but indicates the intent for future enhancements)
120121 else :
121122 start_ids = torch .as_tensor ([input_ids ], device = device )
122123
@@ -143,34 +144,40 @@ def generate_stream(
143144 past_key_values = out .past_key_values
144145
145146 if logprobs is not None :
146- # Prefull logprobs for the prompt.
147- shift_input_ids = start_ids [..., 1 :].contiguous ()
148- shift_logits = logits [..., :- 1 , :].contiguous ()
149- shift_logits = torch .log_softmax (shift_logits , dim = - 1 ).tolist ()
150- for label_id , logit in zip (
151- shift_input_ids [0 ].tolist (), shift_logits [0 ]
152- ):
153- token_logprobs .append (logit [label_id ])
154- # Add empty top_logprobs during prefill (would need to reconstruct full logits tensor to get these)
155- top_logprobs_list .append ({})
147+ if model .config .is_encoder_decoder :
148+ # For encoder-decoder models, we only compute logprobs for generated tokens
149+ # No prefill logprobs needed since encoder input doesn't contribute to generation logprobs
150+ pass
151+ else :
152+ # Prefull logprobs for the prompt.
153+ shift_input_ids = start_ids [..., 1 :].contiguous ()
154+ shift_logits = logits [..., :- 1 , :].contiguous ()
155+ shift_logits = torch .log_softmax (shift_logits , dim = - 1 ).tolist ()
156+ for label_id , logit in zip (
157+ shift_input_ids [0 ].tolist (), shift_logits [0 ]
158+ ):
159+ token_logprobs .append (logit [label_id ])
160+ # Add empty top_logprobs during prefill (would need to reconstruct full logits tensor to get these)
161+ top_logprobs_list .append ({})
156162 else : # decoding
157163 if model .config .is_encoder_decoder :
164+ # For encoder-decoder, use the last generated token or full sequence if interrupted
165+ decoder_input = torch .as_tensor (
166+ [[output_ids [- 1 ]] if not sent_interrupt and len (output_ids ) > input_echo_len else output_ids [input_echo_len :]],
167+ device = device ,
168+ )
158169 out = model .decoder (
159- input_ids = torch .as_tensor (
160- [[token ] if not sent_interrupt else output_ids ],
161- device = device ,
162- ),
170+ input_ids = decoder_input ,
163171 encoder_hidden_states = encoder_output ,
164172 use_cache = True ,
165173 past_key_values = past_key_values if not sent_interrupt else None ,
166174 )
167175 sent_interrupt = False
168-
169176 logits = model .lm_head (out [0 ])
170177 else :
171178 out = model (
172179 input_ids = torch .as_tensor (
173- [[token ] if not sent_interrupt else output_ids ],
180+ [[output_ids [ - 1 ] ] if not sent_interrupt else output_ids ],
174181 device = device ,
175182 ),
176183 use_cache = True ,
@@ -252,30 +259,66 @@ def generate_stream(
252259 )
253260 ret_logprobs = None
254261 if logprobs is not None :
255- # Calculate the start position for this streaming chunk
256- if echo :
257- start_pos = last_sent_token_pos
258- tokens_to_send = output_ids [start_pos :]
262+ if model .config .is_encoder_decoder :
263+ # For encoder-decoder models, calculate logprobs differently
264+ # We only track generated tokens (not input tokens)
265+ generated_tokens_count = len (output_ids ) - input_echo_len
266+ if generated_tokens_count > 0 :
267+ # Calculate the start position for this streaming chunk
268+ if echo :
269+ # For echo=True with encoder-decoder, we still only show generated tokens
270+ start_pos = max (last_sent_token_pos , input_echo_len )
271+ else :
272+ start_pos = max (last_sent_token_pos , input_echo_len )
273+
274+ tokens_to_send = output_ids [start_pos :]
275+
276+ # Update last sent position for next stream chunk
277+ last_sent_token_pos = len (output_ids )
278+
279+ # For encoder-decoder, logprobs start from the first generated token
280+ logprobs_start_idx = start_pos - input_echo_len + 1 # +1 because token_logprobs[0] is None
281+ logprobs_end_idx = len (output_ids ) - input_echo_len + 1
282+
283+ if logprobs_start_idx < len (token_logprobs ) and tokens_to_send :
284+ ret_logprobs = {
285+ "text_offset" : [],
286+ "tokens" : [tokenizer .decode ([token ]) for token in tokens_to_send ],
287+ "token_logprobs" : token_logprobs [logprobs_start_idx :logprobs_end_idx ],
288+ "top_logprobs" : top_logprobs_list [logprobs_start_idx :logprobs_end_idx ],
289+ }
290+
291+ # Compute text_offset for just this chunk
292+ curr_pos = 0
293+ for text in ret_logprobs ["tokens" ]:
294+ ret_logprobs ["text_offset" ].append (curr_pos )
295+ curr_pos += len (text )
259296 else :
260- start_pos = max (last_sent_token_pos , input_echo_len )
261- tokens_to_send = output_ids [start_pos :]
262-
263- # Update last sent position for next stream chunk
264- last_sent_token_pos = len (output_ids )
265-
266- # Format response with only new tokens
267- ret_logprobs = {
268- "text_offset" : [],
269- "tokens" : [tokenizer .decode (token ) for token in tokens_to_send ],
270- "token_logprobs" : token_logprobs [start_pos :],
271- "top_logprobs" : top_logprobs_list [start_pos :],
272- }
273-
274- # Compute text_offset for just this chunk
275- curr_pos = 0
276- for text in ret_logprobs ["tokens" ]:
277- ret_logprobs ["text_offset" ].append (curr_pos )
278- curr_pos += len (text )
297+ # Original logic for causal LM models
298+ # Calculate the start position for this streaming chunk
299+ if echo :
300+ start_pos = last_sent_token_pos
301+ tokens_to_send = output_ids [start_pos :]
302+ else :
303+ start_pos = max (last_sent_token_pos , input_echo_len )
304+ tokens_to_send = output_ids [start_pos :]
305+
306+ # Update last sent position for next stream chunk
307+ last_sent_token_pos = len (output_ids )
308+
309+ # Format response with only new tokens
310+ ret_logprobs = {
311+ "text_offset" : [],
312+ "tokens" : [tokenizer .decode (token ) for token in tokens_to_send ],
313+ "token_logprobs" : token_logprobs [start_pos :],
314+ "top_logprobs" : top_logprobs_list [start_pos :],
315+ }
316+
317+ # Compute text_offset for just this chunk
318+ curr_pos = 0
319+ for text in ret_logprobs ["tokens" ]:
320+ ret_logprobs ["text_offset" ].append (curr_pos )
321+ curr_pos += len (text )
279322
280323 # TODO: For the issue of incomplete sentences interrupting output, apply a patch and others can also modify it to a more elegant way
281324 if judge_sent_end and stopped and not is_sentence_complete (output ):
0 commit comments