@@ -112,7 +112,8 @@ def __init__(
112112 swa_full : Optional [bool ] = None ,
113113 kv_unified : Optional [bool ] = None ,
114114 # HybridCheckpointCache Params
115- ctx_checkpoints : int = 16 ,
115+ ctx_checkpoints : int = 32 ,
116+ checkpoint_interval : int = 4096 ,
116117 # Sampling Params
117118 last_n_tokens_size : int = 64 ,
118119 # LoRA Params
@@ -203,6 +204,7 @@ def __init__(
203204 swa_full: whether to use full-size SWA cache
204205 kv_unified: use single unified KV buffer for the KV cache of all sequences
205206 ctx_checkpoints: max number of context checkpoints to create per slot (default: 16)[(more info)](https://github.com/ggml-org/llama.cpp/pull/15293)
207+ checkpoint_interval: Hybrid model checkpoint token intervals, and archiving of text with interval sizes along the way.
206208 last_n_tokens_size: Maximum number of tokens to keep in the last_n_tokens deque.
207209 lora_base: Optional path to base model, useful if using a quantized base model and you want to apply LoRA to an f16 model.
208210 lora_path: Path to a LoRA file to apply to the model.
@@ -485,10 +487,11 @@ def __init__(
485487 if self .is_hybrid :
486488 if self .verbose :
487489 print (f"Llama.__init__: Hybrid/Recurrent model detected."
488- f"(is_recurrent: { _is_recurrent } , is_hybrid: { _is_hybrid } , n_swa: { _n_swa } ) , swa_full: { swa_full } . "
489- f" Enabling HybridCheckpointCache(ctx_checkpoints={ ctx_checkpoints } )." ,
490+ f"(is_recurrent: { _is_recurrent } , is_hybrid: { _is_hybrid } , n_swa: { _n_swa } , swa_full: { swa_full } ) . "
491+ f" Enabling HybridCheckpointCache(ctx_checkpoints={ ctx_checkpoints } , checkpoint_interval= { checkpoint_interval } )." ,
490492 file = sys .stderr )
491493 self .ctx_checkpoints = ctx_checkpoints
494+ self .checkpoint_interval = checkpoint_interval
492495 self ._hybrid_cache_mgr = HybridCheckpointCache (self ._ctx .ctx , max_checkpoints = self .ctx_checkpoints , verbose = self .verbose )
493496 else :
494497 self ._hybrid_cache_mgr = None
@@ -784,7 +787,7 @@ def eval(self, tokens: Sequence[int]):
784787 if n_eval == 0 :
785788 return
786789
787- # Context Shift
790+ # Context Shift: Prevent OOM by discarding older tokens when context limit is reached.
788791 if self .n_tokens + n_eval > self ._n_ctx :
789792 if self .is_hybrid :
790793 raise RuntimeError (
@@ -793,7 +796,7 @@ def eval(self, tokens: Sequence[int]):
793796 )
794797 else :
795798 _n_keep = min (self .n_keep , self .n_tokens )
796- # number of tokens after n_keep that may be discarded when shifting context
799+ # Number of tokens after n_keep that may be discarded when shifting context
797800 # defaults to half
798801 _n_discard = (self .n_tokens - _n_keep ) // 2
799802
@@ -810,56 +813,108 @@ def eval(self, tokens: Sequence[int]):
810813
811814 self .n_tokens -= _n_discard
812815
813- for i in range (0 , n_eval , self .n_batch ):
814- batch_tokens = tokens [i : min (n_eval , i + self .n_batch )]
815- n_batch_tokens = len (batch_tokens )
816+ # Adaptive batch downgrade limit initialization
817+ current_max_batch = self .n_batch
818+ last_ckpt_pos = self .n_tokens
819+
820+ # If KV slots are full, `current_batch_size` will be halved.
821+ # A `while` loop allows us to correctly resume from the exact cut-off point.
822+ i = 0
823+ while i < n_eval :
824+ # Chunk the tokens using the adaptive current_max_batch
825+ n_chunk = min (n_eval - i , current_max_batch )
826+ chunk = tokens [i : i + n_chunk ]
816827 n_past = self .n_tokens
817828
818829 self ._batch .reset ()
819830
820- pos_array = [self .n_tokens + j for j in range (n_batch_tokens )]
831+ pos_array = [self .n_tokens + j for j in range (n_chunk )]
821832
833+ # Configure logits extraction:
834+ # If _logits_all is True, calculate for every token.
835+ # Otherwise, only calculate for the very last token in the entire evaluation sequence.
822836 if self ._logits_all :
823- logits_array = [True ] * n_batch_tokens
837+ logits_array = [True ] * n_chunk
824838 else :
825- logits_array = [False ] * n_batch_tokens
826- if i + n_batch_tokens == n_eval :
839+ logits_array = [False ] * n_chunk
840+ if i + n_chunk == n_eval :
827841 logits_array [- 1 ] = True
828842
829843 self ._batch .add_sequence (
830- token_array = batch_tokens ,
844+ token_array = chunk ,
831845 pos_array = pos_array ,
832846 seq_ids = [0 ],
833847 logits_array = logits_array
834848 )
835- current_batch_size = n_batch_tokens
836- try :
837- self ._ctx .decode (self ._batch )
838- except Exception as e :
839- raise RuntimeError (
840- f"Decode Failed at "
841- f"Batch size: { n_batch_tokens } . "
842- f"Error: { str (e )} ."
843- ) from e
844849
845- # Save tokens
846- self .input_ids [n_past : n_past + n_batch_tokens ] = batch_tokens
850+ # Dynamic Batch Downgrade: Attempt to decode, reduce batch size if KV cache is fragmented
851+ current_batch_size = n_chunk
852+ success = False
847853
848- # Save logits
849- logits_ptr = self ._ctx .get_logits ()
854+ while current_batch_size > 0 :
855+ # Tell the C++ backend to only process up to `current_batch_size` tokens
856+ self ._batch .batch .n_tokens = current_batch_size
857+
858+ try :
859+ status = self ._ctx .decode (self ._batch )
860+
861+ # 0: Success
862+ if status == 0 :
863+ success = True
864+ # If we successfully decoded after a downgrade,
865+ # update current_max_batch to prevent repeated failures in next iterations.
866+ if current_batch_size < current_max_batch :
867+ current_max_batch = current_batch_size
868+ break
869+
870+ # 1: No KV slot available (Recoverable)
871+ elif status == 1 :
872+ if self .verbose :
873+ print (f"Llama.eval: KV slots full (Code 1). Halving batch size "
874+ f"from { current_batch_size } to { current_batch_size // 2 } ..." , file = sys .stderr )
875+ current_batch_size //= 2
876+
877+ except Exception as e :
878+ # Catch fatal backend failures (e.g., Code -2, -3)
879+ raise RuntimeError (f"Llama.eval(decode): Fatal Decode Error at Pos { self .n_tokens } , "
880+ f"Batch size { current_batch_size } : { str (e )} " ) from e
881+
882+ if not success :
883+ raise RuntimeError ("Llama.eval(decode): Failed completely even with batch size 1." )
884+
885+ # Save successfully processed tokens into the Python-side ledger
886+ self .input_ids [n_past : n_past + current_batch_size ] = chunk [:current_batch_size ]
887+
888+ # Extract and save all logits if requested, ensuring we only copy the successfully processed rows
850889 if self ._logits_all :
851- rows = n_batch_tokens
890+ logits_ptr = self ._ctx .get_logits ()
891+ rows = current_batch_size
852892 cols = self ._n_vocab
853893 logits_view = np .ctypeslib .as_array (logits_ptr , shape = (rows * cols ,))
854- self .scores [n_past : n_past + n_batch_tokens , :].reshape (- 1 )[:] = logits_view
855- else :
856- logits_view = np .ctypeslib .as_array (logits_ptr , shape = (self ._n_vocab ,))
857- self .scores [0 , :] = logits_view
894+ self .scores [n_past : n_past + current_batch_size , :].reshape (- 1 )[:] = logits_view
858895
859- # Update n_tokens
896+ # Update indices based on actual processed batch size
860897 self .n_tokens += current_batch_size
861898 i += current_batch_size
862899
900+ # Periodic Checkpoint: Save states for hybrid models to avoid massive rollbacks
901+ if self .is_hybrid and self ._hybrid_cache_mgr is not None :
902+ if (self .n_tokens - last_ckpt_pos >= self .checkpoint_interval ) and (i < n_eval ):
903+ if self .verbose :
904+ print (f"Llama.eval: [Periodic Checkpoint] Saving hybrid state at pos { self .n_tokens } ." , file = sys .stderr )
905+ self ._hybrid_cache_mgr .save_checkpoint (
906+ current_pos = self .n_tokens ,
907+ tokens = self .input_ids [:self .n_tokens ].tolist (),
908+ seq_id = 0
909+ )
910+ last_ckpt_pos = self .n_tokens
911+
912+ # Save the final logit if not in _logits_all mode
913+ if not self ._logits_all :
914+ logits_ptr = self ._ctx .get_logits ()
915+ logits_view = np .ctypeslib .as_array (logits_ptr , shape = (self ._n_vocab ,))
916+ self .scores [0 , :] = logits_view
917+
863918 # Helper method: Convert dict logit_bias to List[llama_logit_bias]
864919 def _convert_logit_bias (self , logit_bias : Optional [Dict [int , float ]]) -> List [llama_cpp .llama_logit_bias ]:
865920 if not logit_bias :
0 commit comments