Skip to content

Commit e5dbfad

Browse files
authored
Merge branch 'JamePeng:main' into main
2 parents 60e1015 + 39afb9b commit e5dbfad

3 files changed

Lines changed: 118 additions & 39 deletions

File tree

llama_cpp/_internals.py

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -529,19 +529,43 @@ def encode(self, batch: LlamaBatch):
529529
if return_code != 0:
530530
raise RuntimeError(f"llama_encode returned {return_code}")
531531

532-
def decode(self, batch: LlamaBatch):
532+
def decode(self, batch: 'LlamaBatch') -> int:
533+
"""
534+
Evaluate the batch of tokens using the transformer model.
535+
536+
This method executes the forward pass. If the KV cache is heavily fragmented
537+
or out of space, it may return 1, indicating the caller should try to reduce
538+
the batch size or evict idle sequences.
539+
540+
Returns:
541+
0: Success.
542+
1: No KV slot available (Recoverable). The caller should implement a
543+
fallback strategy, such as reducing the batch size and retrying.
544+
545+
Raises:
546+
RuntimeError: If a fatal, non-recoverable error occurs during decoding
547+
(e.g., negative error codes or invalid batch structures).
548+
"""
533549
return_code = llama_cpp.llama_decode(self.ctx, batch.batch)
534550

535551
if return_code == 0:
536-
return
552+
return 0
553+
554+
# 1 means "No KV slot available".
555+
# We explicitly return 1 instead of raising an exception so that the caller
556+
# can gracefully handle it via dynamic batch sizing (batch_size //= 2).
557+
elif return_code == 1:
558+
return 1
537559

560+
# Any other code indicates a fatal failure.
538561
error_map = {
539-
1: "No KV slot available: try reducing batch size or increasing context window",
540-
2: "Decoding aborted",
541-
-1: "Invalid input batch",
562+
2: "Decoding aborted by user callback",
563+
-1: "Invalid input batch (e.g. n_tokens == 0 or exceeding capacity)",
564+
-2: "Could not allocate space for the compute graph (VRAM exhausted)",
565+
-3: "Graph computation failed internally",
542566
}
543567

544-
msg = error_map.get(return_code, "Fatal internal error")
568+
msg = error_map.get(return_code, "Unknown fatal internal error")
545569
raise RuntimeError(f"llama_decode failed (code {return_code}): {msg}")
546570

547571
def set_n_threads(self, n_threads: int, n_threads_batch: int):

llama_cpp/llama.py

Lines changed: 87 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -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:

vendor/llama.cpp

0 commit comments

Comments
 (0)