77from typing import TYPE_CHECKING , Callable , Iterator , Optional
88
99import numpy as np
10+ from numpy .typing import NDArray
1011
1112from ..._parser import TokenParser
1213from ..._schema import (
@@ -76,12 +77,11 @@ class Engine(ABC):
7677 Server so a single server can serve many clients' model objects through a single Engine object.
7778 """
7879
79- def __init__ (self , tokenizer : Tokenizer , compute_log_probs = False , enable_backtrack = True , enable_ff_tokens = True ,
80+ def __init__ (self , tokenizer : Tokenizer , enable_backtrack = True , enable_ff_tokens = True ,
8081 enable_monitoring = True , ** kwargs ):
8182 from ...registry import get_monitor
8283
8384 self .tokenizer = tokenizer
84- self .compute_log_probs = compute_log_probs
8585 self ._enable_backtrack = enable_backtrack
8686 self ._enable_ff_tokens = enable_ff_tokens
8787 self ._enable_monitoring = enable_monitoring
@@ -165,8 +165,8 @@ def __call__(
165165 enable_ff_tokens = self .enable_ff_tokens ,
166166 )
167167
168+ last_temperature = 1.0
168169 engine_output = None
169- logits_lat_ms = 0
170170 while not parser .done ():
171171 t0 = time .time ()
172172
@@ -197,18 +197,26 @@ def __call__(
197197 # (model + prompt) + grammar == model + (prompt + grammar)
198198 tokens = self .tokenizer .recode (tokens )
199199
200- # Note that has_pending_stop implies that the response is a stop response,
201- # but the converse is not true. We can therefore avoid some (but not all)
202- # unnecessary calls to get_logits on the final iteration.
203- has_pending_stop = parser .has_pending_stop ()
204-
205- if not has_pending_stop :
200+ # We can avoid a final get_logits call in the case that:
201+ # 1. The parser has a pending stop
202+ # 2. There are no ff_tokens (except for our last generated token)
203+ # TODO: allow avoiding final forward pass if metrics are disabled
204+ # and we have a pending stop
205+ if parser .has_pending_stop () and (
206+ (not ff_tokens )
207+ or (
208+ len (ff_tokens ) == 1
209+ and engine_output is not None
210+ and ff_tokens [0 ] == engine_output .issued_token .token_id
211+ )
212+ ):
213+ logits = None
214+ logits_lat_ms = 0.0
215+ else :
206216 t1 = time .time ()
207- logits = self .get_logits (token_ids = tokens )
217+ logits = self .get_logits (token_ids = tokens , full_sequence = True )
208218 logits_lat_ms = (time .time () - t1 ) * 1000
209- else :
210- # Avoid calling get_logits if we know we won't use it
211- logits = None
219+
212220
213221 # Important: don't wait on this future until after getting the logits;
214222 # this allows the mask to be built concurrently with model inference
@@ -221,13 +229,31 @@ def __call__(
221229 # if it exists
222230 ff_lat_ms -= logits_lat_ms
223231
232+ if logits is not None :
233+ # Not the last one -- that's for the *next* token.
234+ ff_logits = logits [max (len (logits )- len (ff_tokens )- 1 , 0 ):- 1 ]
235+ ff_probs = (
236+ softmax (np .array (ff_logits ))
237+ if last_temperature < 0.0001
238+ else softmax (np .array (ff_logits ) / last_temperature )
239+ )
240+ if ff_probs .shape [0 ] != len (ff_tokens ):
241+ assert ff_probs .shape [0 ] == len (ff_tokens ) - 1
242+ # We didn't have a BOS token, so we need to fake the first token prob (say... 1?)
243+ ff_probs = np .concatenate (
244+ (np .ones ((1 , ff_probs .shape [1 ])), ff_probs ), axis = 0
245+ )
246+ else :
247+ ff_probs = np .empty (shape = ())
248+
224249 gen_tokens = []
225250 if engine_output is None :
226- for token_id in ff_tokens :
251+ for i , token_id in enumerate ( ff_tokens ) :
227252 gen_tokens .append (
228253 GenToken (
229254 token_id = token_id ,
230255 bytes = self .tokenizer .decode ([token_id ]),
256+ prob = ff_probs [i , token_id ],
231257 # amortize latency
232258 latency_ms = ff_lat_ms / len (ff_tokens ),
233259 is_input = True ,
@@ -240,11 +266,12 @@ def __call__(
240266 ff_start_index = 0
241267 else :
242268 ff_start_index = 1
243- for token_id in ff_tokens [ff_start_index :]:
269+ for i , token_id in enumerate ( ff_tokens [ff_start_index :], start = ff_start_index ) :
244270 gen_tokens .append (
245271 GenToken (
246272 token_id = token_id ,
247273 bytes = self .tokenizer .decode ([token_id ]),
274+ prob = ff_probs [i , token_id ],
248275 # amortize latency
249276 latency_ms = ff_lat_ms / len (ff_tokens [ff_start_index :]),
250277 is_force_forwarded = True ,
@@ -272,10 +299,8 @@ def __call__(
272299 # Ensure we break AFTER yielding the final response
273300 break
274301
275- # If there was a pending stop, we should have broken out of the loop
276- assert not has_pending_stop
277-
278302 # Help the type checker: assert that everything we need to get the next token is not None
303+ assert logits is not None
279304 assert mask is not None
280305 assert ll_response .temperature is not None
281306
@@ -294,14 +319,15 @@ def __call__(
294319 mask_for_sampling = mask
295320
296321 engine_output = self .get_next_token_with_top_k (
297- logits = logits ,
322+ logits = logits [ - 1 , :] ,
298323 logits_lat_ms = logits_lat_ms ,
299324 token_ids = tokens ,
300325 mask = mask_for_sampling ,
301326 temperature = ll_response .temperature ,
302327 k = self ._top_k ,
303328 force_return_unmasked_probs = echo ,
304329 )
330+ last_temperature = ll_response .temperature
305331
306332 if can_finish_early and not mask [engine_output .issued_token .token_id ]:
307333 # Type checker needs some help
@@ -310,8 +336,8 @@ def __call__(
310336
311337 def get_next_token_with_top_k (
312338 self ,
313- logits : Optional [ np . ndarray ] ,
314- logits_lat_ms : Optional [ float ] ,
339+ logits : NDArray ,
340+ logits_lat_ms : float ,
315341 token_ids : list [int ],
316342 mask : Optional [bytes ],
317343 temperature : float ,
@@ -322,7 +348,7 @@ def get_next_token_with_top_k(
322348
323349 Parameters
324350 -------
325- logits : Optional[np.ndarray ]
351+ logits : Optional[NDArray ]
326352 The logits for the current token ids in the sequence.
327353 If None, the model will call get_logits to get the logits.
328354 logits_lat_ms: Optional[float]
@@ -345,38 +371,7 @@ def get_next_token_with_top_k(
345371 The output from the model.
346372 """
347373
348- if logits is None :
349- t0 = time .time ()
350- try :
351- logits = self .get_logits (token_ids = token_ids )
352- except NotImplementedError :
353- # fallback to orignal get_next_token method
354- _t0 = time .time ()
355- token_id = self .get_next_token (
356- token_ids = token_ids ,
357- mask = mask ,
358- temperature = temperature ,
359- )
360- _lat = (time .time () - _t0 ) * 1000
361-
362- _issued_token = GenToken (
363- token_id = token_id ,
364- prob = 1.0 ,
365- bytes = self .tokenizer .decode ([token_id ]),
366- latency_ms = _lat ,
367- is_generated = True ,
368- )
369-
370- return EngineOutput (
371- issued_token = _issued_token ,
372- top_k = [_issued_token ],
373- masked_top_k = [_issued_token ] if mask is not None else None ,
374- )
375- lat_ms = (time .time () - t0 ) * 1000
376- else :
377- lat_ms = logits_lat_ms
378-
379- def get_top_k (_probs : np .ndarray , _k : int = 5 ) -> list [GenToken ]:
374+ def get_top_k (_probs : NDArray , _k : int = 5 ) -> list [GenToken ]:
380375 top_k_indices = np .argsort (_probs )[::- 1 ][:_k ]
381376 top_k_probs = _probs [top_k_indices ]
382377
@@ -385,7 +380,7 @@ def get_top_k(_probs: np.ndarray, _k: int = 5) -> list[GenToken]:
385380 token_id = token ,
386381 prob = prob ,
387382 bytes = self .tokenizer .decode ([token ]),
388- latency_ms = lat_ms ,
383+ latency_ms = logits_lat_ms ,
389384 is_generated = True ,
390385 )
391386 for token , prob in zip (top_k_indices , top_k_probs )
@@ -435,7 +430,7 @@ def get_top_k(_probs: np.ndarray, _k: int = 5) -> list[GenToken]:
435430 token_id = sampled_index ,
436431 prob = sampled_prob ,
437432 bytes = self .tokenizer .decode ([sampled_index ]),
438- latency_ms = lat_ms ,
433+ latency_ms = logits_lat_ms ,
439434 is_generated = True ,
440435 )
441436
@@ -449,15 +444,11 @@ def get_top_k(_probs: np.ndarray, _k: int = 5) -> list[GenToken]:
449444 return output
450445
451446 @abstractmethod
452- def get_logits (self , token_ids : list [int ]) -> np . ndarray :
447+ def get_logits (self , token_ids : list [int ], full_sequence : bool = False ) -> NDArray :
453448 pass
454449
455- def get_per_token_topk_probs (self , token_ids : list [int ], top_k : int = 5 ) -> list [GenToken ]:
456- """Get the top-k probabilities for each token in the sequence."""
457- raise NotImplementedError
458-
459450 def sample_with_temperature (
460- self , logits : np . ndarray , mask : Optional [bytes ], temperature : float
451+ self , logits : NDArray , mask : Optional [bytes ], temperature : float
461452 ) -> int :
462453 if mask is not None :
463454 logits += np .frombuffer (mask , dtype = np .uint8 )
0 commit comments