@@ -106,11 +106,23 @@ def __init__(
106106 # key -> {position: continuation}
107107 #
108108 # A dict is used so that recent entries can be refreshed when more continuation
109- # tokens become available.
109+ # tokens become available. Draft selection is based on continuation frequency,
110+ # not just the most recent continuation.
110111 self ._map_k4v : DefaultDict [
111112 Tuple [int , ...], Dict [int , Tuple [int , ...]]
112113 ] = collections .defaultdict (dict )
113114
115+ # Acceptance feedback, aligned with llama.cpp's ngram-map behavior:
116+ # accept(n) stores how many tokens were accepted for the key/value used by
117+ # the previous draft and limits the future draft length for that key/value.
118+ self ._accepted_k : Dict [Tuple [int , ...], int ] = {}
119+ self ._accepted_k4v : DefaultDict [
120+ Tuple [int , ...], Dict [Tuple [int , ...], int ]
121+ ] = collections .defaultdict (dict )
122+
123+ self ._last_draft_key : Optional [Tuple [int , ...]] = None
124+ self ._last_draft_value : Optional [Tuple [int , ...]] = None
125+
114126 self ._closed = False
115127 self ._last_draft_len = 0
116128
@@ -124,6 +136,10 @@ def clear(self) -> None:
124136 self ._history .clear ()
125137 self ._map_k .clear ()
126138 self ._map_k4v .clear ()
139+ self ._accepted_k .clear ()
140+ self ._accepted_k4v .clear ()
141+ self ._last_draft_key = None
142+ self ._last_draft_value = None
127143 self ._last_draft_len = 0
128144
129145 def close (self ) -> None :
@@ -147,13 +163,27 @@ def accept(self, n_accepted: int) -> None:
147163 """
148164 Notify how many draft tokens were accepted by the target model.
149165
150- This implementation does not need to update internal state here, because the
151- next call receives the verified token history through `input_ids`.
152-
153- The method is kept for API symmetry and future extensions, such as acceptance
154- statistics, adaptive reset, or low-acceptance fallback.
166+ The accepted length is written back to the key/value used by the previous
167+ draft. Future drafts for the same key/value are truncated to this accepted
168+ length, matching llama.cpp's ngram-map feedback loop.
155169 """
156- return
170+ if n_accepted < 0 :
171+ raise ValueError ("n_accepted must be non-negative" )
172+
173+ if self ._last_draft_key is None or self ._last_draft_len <= 0 :
174+ return
175+
176+ accepted = min (int (n_accepted ), self ._last_draft_len )
177+
178+ if self .mode == "k" :
179+ self ._accepted_k [self ._last_draft_key ] = accepted
180+ else :
181+ if self ._last_draft_value is not None :
182+ self ._accepted_k4v [self ._last_draft_key ][self ._last_draft_value ] = accepted
183+
184+ self ._last_draft_key = None
185+ self ._last_draft_value = None
186+ self ._last_draft_len = 0
157187
158188 def _sync_and_index (self , input_ids : npt .NDArray [np .intc ]) -> None :
159189 """
@@ -231,9 +261,11 @@ def _sync_and_index(self, input_ids: npt.NDArray[np.intc]) -> None:
231261 for pos in range (start , end ):
232262 key_start = pos
233263 value_start = pos + self .ngram_size
234- value_end = min ( value_start + self .num_pred_tokens , len ( self . _history ))
264+ value_end = value_start + self .num_pred_tokens
235265
236- if value_start >= value_end :
266+ # K4V tracks fixed-size continuation m-grams. Partial tail values are
267+ # intentionally skipped so frequency statistics remain comparable.
268+ if value_end > len (self ._history ):
237269 continue
238270
239271 key = tuple (self ._history [key_start :value_start ])
@@ -267,6 +299,8 @@ def __call__(
267299 _ = kwargs
268300
269301 self ._sync_and_index (input_ids )
302+ self ._last_draft_key = None
303+ self ._last_draft_value = None
270304 self ._last_draft_len = 0
271305
272306 if len (self ._history ) < self .ngram_size :
@@ -276,28 +310,57 @@ def __call__(
276310
277311 if self .mode == "k" :
278312 positions = self ._map_k .get (search_key )
279- if not positions or len ( positions ) < self . min_hits :
313+ if not positions :
280314 return np .array ([], dtype = np .intc )
281315
282- # Use the latest valid match with an available continuation.
316+ # Key-only mode follows llama.cpp's ngram-map-k behavior: once a key
317+ # match is found, draft from the latest valid match. min_hits is not
318+ # used as a confidence gate for key-only mode.
283319 draft : List [int ] = []
320+ accepted_limit = self ._accepted_k .get (search_key , self .num_pred_tokens )
321+ if accepted_limit <= 0 :
322+ return np .array ([], dtype = np .intc )
323+
284324 for pos in reversed (positions ):
285325 start = pos + self .ngram_size
286326 if start < len (self ._history ):
287- end = min (start + self . num_pred_tokens , len (self ._history ))
327+ end = min (start + accepted_limit , len (self ._history ))
288328 draft = self ._history [start :end ]
289329 break
290330
331+ self ._last_draft_key = search_key
332+
291333 else :
292334 values = self ._map_k4v .get (search_key )
293335 if not values or len (values ) < self .min_hits :
294336 return np .array ([], dtype = np .intc )
295337
296- # Use the continuation from the latest historical position.
297- latest_pos = max (values )
298- draft = list (values [latest_pos ])
338+ # K4V mode chooses the most frequent continuation m-gram rather than the
339+ # latest one. If the strongest continuation is not at least twice as
340+ # frequent as all other continuations combined, skip drafting.
341+ counts = collections .Counter (values .values ())
342+ best_value , best_count = counts .most_common (1 )[0 ]
343+ other_count = sum (counts .values ()) - best_count
344+
345+ if other_count > 0 and best_count < 2 * other_count :
346+ return np .array ([], dtype = np .intc )
347+
348+ accepted_limit = self ._accepted_k4v [search_key ].get (
349+ best_value , self .num_pred_tokens
350+ )
351+ if accepted_limit <= 0 :
352+ return np .array ([], dtype = np .intc )
353+
354+ draft = list (best_value [:accepted_limit ])
355+ self ._last_draft_key = search_key
356+ self ._last_draft_value = best_value
299357
300358 self ._last_draft_len = len (draft )
359+ if self ._last_draft_len <= 0 :
360+ self ._last_draft_key = None
361+ self ._last_draft_value = None
362+ return np .array ([], dtype = np .intc )
363+
301364 return np .asarray (draft , dtype = np .intc )
302365
303366
0 commit comments