2121#include < exception>
2222#include < memory>
2323#include < filesystem>
24+ #include < limits>
2425#include < utility>
2526#include < fstream>
2627
@@ -2122,30 +2123,35 @@ struct server_context_impl {
21222123 // n_tokens_cur: the number of tokens added to the batch for the current slot
21232124 void create_checkpoint (server_slot & slot, const int64_t n_tokens_cur, llama_pos pos_min, llama_pos pos_max) {
21242125 while (slot.prompt .checkpoints .size () >= (size_t ) params_base.n_ctx_checkpoints ) {
2125- // Preserve the oldest anchor and the most recent checkpoint . Removing the checkpoint
2126+ // Preserve early prefix anchors and the most recent checkpoints . Removing a checkpoint
21262127 // from the densest interior interval keeps coverage across the full prompt history.
21272128 auto erase_it = slot.prompt .checkpoints .begin ();
21282129
2129- if (slot.prompt .checkpoints .size () > 2 ) {
2130- auto prev_it = slot.prompt .checkpoints .begin ();
2130+ if (slot.prompt .checkpoints .size () > 4 ) {
2131+ const size_t n_keep_front = 2 ;
2132+ const size_t n_keep_back = 2 ;
2133+
2134+ auto prev_it = std::next (slot.prompt .checkpoints .begin (), n_keep_front - 1 );
21312135 auto cur_it = std::next (prev_it);
21322136 auto next_it = std::next (cur_it);
2137+ auto last_candidate = std::prev (slot.prompt .checkpoints .end (), n_keep_back);
21332138
21342139 erase_it = cur_it;
2135- int64_t min_merged_span = next_it->n_tokens - prev_it->n_tokens ;
2136-
2137- while (std::next (next_it) != slot.prompt .checkpoints .end ()) {
2138- ++prev_it;
2139- ++cur_it;
2140- ++next_it;
2141-
2140+ int64_t min_merged_span = std::numeric_limits<int64_t >::max ();
2141+ while (cur_it != last_candidate) {
21422142 const int64_t merged_span = next_it->n_tokens - prev_it->n_tokens ;
21432143
21442144 if (merged_span < min_merged_span) {
21452145 erase_it = cur_it;
21462146 min_merged_span = merged_span;
21472147 }
2148+
2149+ ++prev_it;
2150+ ++cur_it;
2151+ ++next_it;
21482152 }
2153+ } else if (slot.prompt .checkpoints .size () > 2 ) {
2154+ erase_it = std::next (slot.prompt .checkpoints .begin ());
21492155 }
21502156
21512157 const auto & cur = *erase_it;
@@ -2915,8 +2921,13 @@ struct server_context_impl {
29152921 const bool is_recurrent_or_hybrid =
29162922 llama_model_is_recurrent (model_tgt) ||
29172923 llama_model_is_hybrid (model_tgt);
2924+ const bool needs_context_checkpoints =
2925+ params_base.n_ctx_checkpoints > 0 &&
2926+ (ctx_tgt_seq_rm_type == COMMON_CONTEXT_SEQ_RM_TYPE_FULL ||
2927+ ctx_tgt_seq_rm_type == COMMON_CONTEXT_SEQ_RM_TYPE_RS ||
2928+ n_swa > 0 );
29182929 slot.n_prompt_tokens_prefix =
2919- is_recurrent_or_hybrid && n_past > 0 ? n_past : -1 ;
2930+ needs_context_checkpoints && n_past > 0 ? n_past : -1 ;
29202931
29212932 // ref: https://github.com/ggml-org/llama.cpp/pull/24110
29222933 const bool has_new_tokens = (n_past < slot.task ->n_tokens ());
@@ -3115,6 +3126,25 @@ struct server_context_impl {
31153126 ctx_tgt_seq_rm_type == COMMON_CONTEXT_SEQ_RM_TYPE_RS ||
31163127 n_swa > 0 );
31173128
3129+ // Anchor a checkpoint at the already processed common-prefix boundary. This
3130+ // is done before adding more tokens because checkpoints capture the current
3131+ // decoded memory state, not the batch that is about to be decoded.
3132+ if (do_checkpoint &&
3133+ slot.n_prompt_tokens_prefix > 0 &&
3134+ slot.prompt .n_tokens () == slot.n_prompt_tokens_prefix ) {
3135+ const auto pos_min = llama_memory_seq_pos_min (llama_get_memory (ctx_tgt), slot.id );
3136+ const auto pos_max = llama_memory_seq_pos_max (llama_get_memory (ctx_tgt), slot.id );
3137+ const bool is_spaced =
3138+ slot.prompt .checkpoints .empty () ||
3139+ slot.prompt .n_tokens () > slot.prompt .checkpoints .back ().n_tokens + params_base.checkpoint_min_step ;
3140+
3141+ if (pos_min >= 0 && is_spaced) {
3142+ create_checkpoint (slot, 0 , pos_min, pos_max);
3143+ }
3144+
3145+ slot.n_prompt_tokens_prefix = -1 ;
3146+ }
3147+
31183148 bool has_mtmd = false ;
31193149
31203150 // check if we should process the image
@@ -3200,15 +3230,6 @@ struct server_context_impl {
32003230 break ;
32013231 }
32023232
3203- // Anchor a checkpoint at the common-prefix boundary. This avoids
3204- // re-processing the gap to the nearest prompt-end checkpoint on
3205- // subsequent requests that reuse the same prefix.
3206- if (do_checkpoint &&
3207- slot.n_prompt_tokens_prefix > 0 &&
3208- slot.prompt .n_tokens () == slot.n_prompt_tokens_prefix ) {
3209- break ;
3210- }
3211-
32123233 // process the last few tokens of the prompt separately in order to allow for a checkpoint to be created.
32133234 // create checkpoints that many tokens before the end of the prompt:
32143235 // - 4 + n_ubatch
@@ -3235,6 +3256,7 @@ struct server_context_impl {
32353256 const auto n_tokens_cur = batch.n_tokens - n_tokens_prev;
32363257
32373258 const bool near_prompt_end = slot.task ->n_tokens () < slot.prompt .n_tokens () + n_ubatch;
3259+ const int32_t n_tokens_start = slot.prompt .n_tokens () - n_tokens_cur;
32383260
32393261 // entire prompt has been processed
32403262 if (slot.prompt .n_tokens () == slot.task ->n_tokens ()) {
@@ -3259,10 +3281,6 @@ struct server_context_impl {
32593281 const auto pos_min = llama_memory_seq_pos_min (llama_get_memory (ctx_tgt), slot.id );
32603282 const auto pos_max = llama_memory_seq_pos_max (llama_get_memory (ctx_tgt), slot.id );
32613283
3262- // checkpoints are created before the current batch is decoded, so
3263- // their token position is the batch start rather than the prompt end
3264- const int32_t n_tokens_start = slot.prompt .n_tokens () - n_tokens_cur;
3265-
32663284 {
32673285 const bool is_on_user =
32683286 n_before_user_known &&
0 commit comments