char-fork/llama.cpp
git clone https://git.t4t.associates/char-fork/llama.cpp
311d4211b
char/aliceai-t5-moe
1#include "llama-memory-hybrid-idx.h" 2 3#include "llama-impl.h" 4#include "llama-batch.h" 5#include "llama-io.h" 6#include "llama-model.h" 7 8 9#include <algorithm> 10#include <cassert> 11#include <cmath> 12#include <iterator> 13#include <stdexcept> 14 15// 16// llama_memory_hybrid_idx 17// 18 19llama_memory_hybrid_idx::llama_memory_hybrid_idx ( 20const llama_model & model , 21/* attn */ 22ggml_type type_k , 23ggml_type type_v , 24bool v_trans , 25uint32_t kv_size , 26uint32_t n_pad , 27uint32_t n_swa , 28llama_swa_type swa_type , 29/* recurrent */ 30ggml_type type_r , 31ggml_type type_s , 32uint32_t rs_size , 33/* common */ 34uint32_t n_seq_max , 35uint32_t n_rs_seq , 36bool offload , 37bool unified , 38/* layer filters */ 39const layer_filter_cb & filter_attn , 40const layer_filter_cb & filter_recr , 41const layer_filter_cb & filter_idx ) : 42llama_memory_hybrid ( 43model , 44type_k ,type_v ,v_trans ,kv_size ,n_pad ,n_swa ,swa_type , 45type_r ,type_s ,rs_size , 46n_seq_max ,n_rs_seq ,offload ,unified , 47filter_attn ,filter_recr ), 48hparams_idx (model .hparams ), 49mem_idx (filter_idx == nullptr ?nullptr : [& ] { 50// MQA with a single key head of indexer_head_size, as llama_kv_cache_dsa shapes its own 51 std::fill (hparams_idx .n_head_kv_arr .begin (),hparams_idx .n_head_kv_arr .end (),1 ); 52hparams_idx .n_embd_head_k_full = model .hparams .indexer_head_size ; 53 54// the cached indexer keys are raw, rotation happens after pooling at read time, so a 55// K-shift must not rotate them while the stream copies in the same update still apply 56hparams_idx .rope_type = LLAMA_ROPE_TYPE_NONE ; 57 58// fool llama_kv_cache into thinking this is a MLA cache, so it won't cache V tensors 59hparams_idx .n_embd_head_k_mla_impl = model .hparams .indexer_head_size ; 60hparams_idx .n_embd_head_v_mla_impl = model .hparams .indexer_head_size ; 61 62LLAMA_LOG_INFO ("%s: creating indexer KV cache, size = %u cells\n" ,__func__ ,kv_size ); 63 64return new llama_kv_cache ( 65model ,hparams_idx ,type_k ,type_v ,v_trans ,offload ,unified , 66kv_size ,n_seq_max ,n_pad ,n_swa ,swa_type , 67nullptr ,filter_idx ,nullptr ,nullptr ,"idx_" ); 68 }()) {} 69 70llama_memory_context_ptr llama_memory_hybrid_idx::init_batch (llama_batch_allocr & balloc ,uint32_t n_ubatch ,bool embd_all ) { 71// note: repeats llama_memory_hybrid::init_batch, as the indexer needs the attention slot infos that the base context hides 72do { 73balloc .split_reset (); 74 75// follow the recurrent pattern for creating the ubatch splits 76 std::vector < llama_ubatch > ubatches ; 77 78while (true) { 79llama_ubatch ubatch ; 80 81if (embd_all ) { 82// if all tokens are output, split by sequence 83ubatch = balloc .split_seq (n_ubatch ); 84 }else { 85// Use non-sequential split when KV cache is unified (needed for hellaswag/winogrande/multiple-choice) 86const bool unified = (get_mem_attn ()-> get_n_stream ()== 1 ); 87 88// [TAG_RECURRENT_ROLLBACK_SPLITS] 89// the trailing (1 + n_rs_seq) tokens of each seq must stay in the same ubatch 90// so that the rollback snapshots remain valid 91const uint32_t n_rs_seq = get_mem_recr ()-> n_rs_seq ; 92 93ubatch = balloc .split_equal (n_ubatch , !unified ,n_rs_seq > 0 ?n_rs_seq + 1 :0 ); 94 } 95 96if (ubatch .n_tokens == 0 ) { 97break ; 98 } 99 100ubatches .push_back (std::move (ubatch ));// NOLINT 101 } 102 103if (balloc .get_n_used ()< balloc .get_n_tokens ()) { 104// failed to find a suitable split 105break ; 106 } 107 108// prepare the recurrent batches first 109if (!get_mem_recr ()-> prepare (ubatches )) { 110// TODO: will the recurrent cache be in an undefined context at this point? 111LLAMA_LOG_ERROR ("%s: failed to prepare recurrent ubatches\n" ,__func__ ); 112return std::make_unique < llama_memory_hybrid_idx_context > (LLAMA_MEMORY_STATUS_FAILED_PREPARE ); 113 } 114 115// prepare the attention cache 116auto heads_attn = get_mem_attn ()-> prepare (ubatches ); 117if (heads_attn .empty ()) { 118LLAMA_LOG_ERROR ("%s: failed to prepare attention ubatches\n" ,__func__ ); 119return std::make_unique < llama_memory_hybrid_idx_context > (LLAMA_MEMORY_STATUS_FAILED_PREPARE ); 120 } 121 122// the indexer uses the attention cache's slot layout; a separate one can drift from it 123 llama_kv_cache::slot_info_vec_t heads_idx ; 124if (mem_idx ) { 125heads_idx = heads_attn ; 126 } 127 128return std::make_unique < llama_memory_hybrid_idx_context > ( 129this , std::move (heads_attn ), std::move (heads_idx ), std::move (ubatches )); 130 }while (false); 131 132return std::make_unique < llama_memory_hybrid_idx_context > (LLAMA_MEMORY_STATUS_FAILED_PREPARE ); 133} 134 135llama_memory_context_ptr llama_memory_hybrid_idx::init_full () { 136return std::make_unique < llama_memory_hybrid_idx_context > (this ); 137} 138 139llama_memory_context_ptr llama_memory_hybrid_idx::init_update (llama_context * lctx ,bool optimize ) { 140return std::make_unique < llama_memory_hybrid_idx_context > (this ,lctx ,optimize ); 141} 142 143void llama_memory_hybrid_idx::clear (bool data ) { 144 llama_memory_hybrid::clear (data ); 145 146if (mem_idx ) { 147mem_idx -> clear (data ); 148 } 149} 150 151bool llama_memory_hybrid_idx::seq_rm (llama_seq_id seq_id ,llama_pos p0 ,llama_pos p1 ) { 152// same order as llama_memory_hybrid::seq_rm: the recurrent cache can refuse, so try it first 153if (!get_mem_recr ()-> seq_rm (seq_id ,p0 ,p1 )) { 154return false; 155 } 156 157if (mem_idx ) { 158mem_idx -> seq_rm (seq_id ,p0 ,p1 ); 159 } 160 161return get_mem_attn ()-> seq_rm (seq_id ,p0 ,p1 ); 162} 163 164void llama_memory_hybrid_idx::seq_cp (llama_seq_id seq_id_src ,llama_seq_id seq_id_dst ,llama_pos p0 ,llama_pos p1 ) { 165 llama_memory_hybrid::seq_cp (seq_id_src ,seq_id_dst ,p0 ,p1 ); 166 167if (mem_idx ) { 168mem_idx -> seq_cp (seq_id_src ,seq_id_dst ,p0 ,p1 ); 169 } 170} 171 172void llama_memory_hybrid_idx::seq_keep (llama_seq_id seq_id ) { 173 llama_memory_hybrid::seq_keep (seq_id ); 174 175if (mem_idx ) { 176mem_idx -> seq_keep (seq_id ); 177 } 178} 179 180void llama_memory_hybrid_idx::seq_add (llama_seq_id seq_id ,llama_pos p0 ,llama_pos p1 ,llama_pos shift ) { 181 llama_memory_hybrid::seq_add (seq_id ,p0 ,p1 ,shift ); 182 183if (mem_idx ) { 184mem_idx -> seq_add (seq_id ,p0 ,p1 ,shift ); 185 } 186} 187 188void llama_memory_hybrid_idx::seq_div (llama_seq_id seq_id ,llama_pos p0 ,llama_pos p1 ,int d ) { 189 llama_memory_hybrid::seq_div (seq_id ,p0 ,p1 ,d ); 190 191if (mem_idx ) { 192mem_idx -> seq_div (seq_id ,p0 ,p1 ,d ); 193 } 194} 195 196std::map < ggml_backend_buffer_type_t ,size_t > llama_memory_hybrid_idx::memory_breakdown ()const { 197 std::map < ggml_backend_buffer_type_t ,size_t > mb = llama_memory_hybrid::memory_breakdown (); 198 199if (mem_idx ) { 200for (const auto & buft_size :mem_idx -> memory_breakdown ()) { 201mb [buft_size .first ]+= buft_size .second ; 202 } 203 } 204 205return mb ; 206} 207 208void llama_memory_hybrid_idx::state_write (llama_io_write_i & io ,llama_seq_id seq_id ,llama_state_seq_flags flags )const { 209 llama_memory_hybrid::state_write (io ,seq_id ,flags ); 210 211// [TAG_HYBRID_IDX_STATE] the indexer section goes last, so it is a pure suffix: an old reader stops early instead of misparsing it 212// The indexer mirrors the attention cache, so it uses the same PARTIAL_ONLY gate. 213if ((flags & LLAMA_STATE_SEQ_FLAGS_PARTIAL_ONLY )== 0 ) { 214if (mem_idx ) { 215mem_idx -> state_write (io ,seq_id ,flags ); 216 } 217 } 218 219} 220 221void llama_memory_hybrid_idx::state_read (llama_io_read_i & io ,llama_seq_id seq_id ,llama_state_seq_flags flags ) { 222// note: repeats llama_memory_hybrid::state_read 223// the indexer needs the attention cache's cells, and a half-failed restore must leave all three caches alike 224 225// [TAG_HYBRID_IDX_SINFO] 226// the indexer restore adopts the attention cache's layout instead of searching for cells of its own 227// two find_slot calls agree only while both caches see the same occupancy, which a restore cannot promise 228 llama_kv_cache::slot_info_vec_t sinfos_attn ; 229 230try { 231if ((flags & LLAMA_STATE_SEQ_FLAGS_PARTIAL_ONLY )== 0 ) { 232get_mem_attn ()-> state_read_sinfo (io ,seq_id ,flags ,mem_idx ?& sinfos_attn :nullptr ,nullptr ); 233 } 234 235get_mem_recr ()-> state_read (io ,seq_id ,flags ); 236 237// [TAG_HYBRID_IDX_STATE] must mirror the write order in state_write 238if ((flags & LLAMA_STATE_SEQ_FLAGS_PARTIAL_ONLY )== 0 ) { 239if (mem_idx ) { 240mem_idx -> state_read_sinfo (io ,seq_id ,flags ,nullptr ,& sinfos_attn ); 241 } 242 } 243 244 }catch (...) { 245// a half-restored context is the one state the indexer cannot fix by itself: attention holds new cells, the indexer old ones 246// drop what was being restored from all of them, which is a state they do agree on. 247state_drop (seq_id ); 248 249throw ; 250 } 251} 252 253void llama_memory_hybrid_idx::state_drop (llama_seq_id seq_id ) { 254// dropped directly, not via seq_rm: the recurrent cache may refuse it and then only the other two get cleared 255if (seq_id < 0 ) { 256clear (true); 257 258return ; 259 } 260 261get_mem_attn ()-> seq_rm (seq_id ,-1 ,-1 ); 262get_mem_recr ()-> seq_rm (seq_id ,-1 ,-1 ); 263 264if (mem_idx ) { 265mem_idx -> seq_rm (seq_id ,-1 ,-1 ); 266 } 267} 268 269llama_kv_cache * llama_memory_hybrid_idx::get_mem_idx ()const { 270return mem_idx .get (); 271} 272 273void llama_memory_hybrid_idx::set_input_qsa ( 274ggml_tensor * cell_blk , 275ggml_tensor * blk_cells , 276ggml_tensor * blk_pos , 277ggml_tensor * bias , 278const llama_ubatch * ubatch , 279uint32_t ratio , 280bool blk_bias )const { 281GGML_ASSERT (ratio > 0 ); 282GGML_ASSERT (get_mem_idx ()!= nullptr ); 283 284GGML_ASSERT (ggml_backend_buffer_is_host (cell_blk -> buffer )); 285 286const int64_t n_kv = cell_blk -> ne [0 ]; 287const int64_t n_ns = cell_blk -> ne [1 ];// streams in this ubatch 288const int64_t n_blocks = blk_pos -> ne [0 ]/(4 * n_ns ); 289const int64_t n_tokens = ubatch -> n_tokens ; 290const int64_t r = ratio ; 291 292GGML_ASSERT (n_tokens %n_ns == 0 ); 293const int64_t n_tps = n_tokens /n_ns ;// tokens per stream 294 295int32_t * dst_cell_blk = (int32_t * )cell_blk -> data ; 296int32_t * dst_blk_cells = (int32_t * )blk_cells -> data ; 297int32_t * dst_blk_pos = (int32_t * )blk_pos -> data ; 298float * dst_bias = (float * )bias -> data ; 299 300// a block is keyed on (sequence set, index bucket): a unified cache counts every sequence 301// from zero, so the bucket alone would pool two sequences into one block 302GGML_ASSERT (r <=64 ); 303const uint64_t slots_full = r == 64 ? ~uint64_t (0 ) : ((uint64_t (1 ) <<r )- 1 ); 304 305// TODO: this runs per ubatch and is O(n_kv) per stream, about 865 us at 33k context. the cost 306// is the per-cell scan rather than these allocations, so hoisting them buys nothing 307 std::vector < int32_t > blk_of (n_kv ); 308 std::vector < int32_t > cell_grp (n_kv ); 309 std::vector < int32_t > grp_head (n_blocks ); 310 std::vector < int32_t > grp_next ; 311 std::vector < int32_t > grp_first ; 312 std::vector < int32_t > grp_slot0 ; 313 std::vector < uint64_t > grp_slots ; 314 std::vector < int32_t > grp_bid ; 315 std::vector < int32_t > bid_idx ; 316 std::vector < int32_t > bid_cell ; 317 std::vector < int32_t > bid_slot0 ; 318 319 std::vector < int32_t > order ; 320 std::vector < int32_t > rank ; 321 322 std::fill (dst_blk_pos ,dst_blk_pos + 4 * n_blocks * n_ns ,0 ); 323 324for (int64_t s = 0 ;s < n_ns ;++ s ) { 325// ubatch index s*n_tps belongs to this stream; ask which cells array it uses 326const llama_seq_id seq_of_stream = ubatch -> seq_id [s * n_tps ][0 ]; 327const auto & cells = get_mem_idx ()-> get_cells (seq_of_stream ); 328 329int32_t * cur_cell_blk = dst_cell_blk + s * n_kv ; 330int32_t * cur_blk_cells = dst_blk_cells + s * (r * n_blocks ); 331 332 std::fill (cur_blk_cells ,cur_blk_cells + r * n_blocks ,0 ); 333 334bid_idx .clear (); 335bid_cell .clear (); 336bid_slot0 .clear (); 337 338int n_seq_present = 0 ; 339 340for (int sq = 0 ;sq < LLAMA_MAX_SEQ && n_seq_present < 2 ;++ sq ) { 341if (cells .seq_pos_min (sq ) >=0 ) { 342n_seq_present ++ ; 343 } 344 } 345 346const bool one_seq = n_seq_present <=1 ; 347 348// a cell no block covers needs its own -inf, which a per-block bias cannot carry 349// every cache path keeps the position below the cell window, so this stays false 350bool oor = false; 351 352bool dup = false; 353 354bool ranked = false; 355 356auto group_cells = [& ]() { 357// -1 means no usable block: an incomplete or short group cannot be pooled 358 std::fill (blk_of .begin (),blk_of .end (),-1 ); 359 std::fill (cell_grp .begin (),cell_grp .end (),-1 ); 360 std::fill (grp_head .begin (),grp_head .end (),-1 ); 361 362grp_next .clear (); 363grp_first .clear (); 364grp_slot0 .clear (); 365grp_slots .clear (); 366grp_bid .clear (); 367 368oor = false; 369dup = false; 370 371for (int64_t j = 0 ;j < n_kv ;++ j ) { 372if (cells .is_empty (j )) { 373continue ; 374 } 375 376const int64_t idx = ranked ?rank [j ] :cells .pos_get (j ); 377const int64_t pb = idx /r ; 378 379if (pb >=n_blocks ) { 380oor = true; 381continue ; 382 } 383 384int32_t g = -1 ; 385 386for (int32_t c = grp_head [pb ];c >=0 ;c = grp_next [c ]) { 387if (one_seq || cells .seq_get_all ((uint32_t )grp_first [c ])== cells .seq_get_all ((uint32_t )j )) { 388g = c ; 389break ; 390 } 391 } 392 393if (g < 0 ) { 394g = (int32_t )grp_first .size (); 395 396grp_next .push_back (grp_head [pb ]); 397grp_first .push_back ((int32_t )j ); 398grp_slot0 .push_back (-1 ); 399grp_slots .push_back (0 ); 400grp_bid .push_back (-1 ); 401 402grp_head [pb ]= g ; 403 } 404 405const uint64_t bit = uint64_t (1 ) << (idx %r ); 406 407dup |= (grp_slots [g ]& bit )!= 0 ; 408 409cell_grp [j ]= g ; 410grp_slots [g ] |=bit ; 411 412if (idx %r == 0 ) { 413grp_slot0 [g ]= (int32_t )j ; 414 } 415 } 416 }; 417 418group_cells (); 419 420// mrope repeats one position across an image, so rank cells instead of using the position 421if (dup && ubatch -> is_pos_2d ()&& one_seq ) { 422order .clear (); 423order .reserve (n_kv ); 424 425for (int64_t j = 0 ;j < n_kv ;++ j ) { 426if (!cells .is_empty (j )) { 427order .push_back ((int32_t )j ); 428 } 429 } 430 431// same total order the mrope causal mask uses: pos, then ext.y, then ext.x 432 std::sort (order .begin (),order .end (), [& cells ](int32_t a ,int32_t b ) { 433const llama_pos pa = cells .pos_get (a ); 434const llama_pos pb = cells .pos_get (b ); 435 436if (pa != pb ) { 437return pa < pb ; 438 } 439 440const auto & ea = cells .ext_get (a ); 441 442return cells .ext_get (b ).is_2d_gt (ea .x ,ea .y ); 443 }); 444 445rank .assign (n_kv ,-1 ); 446 447for (int64_t k = 0 ;k < (int64_t )order .size ();++ k ) { 448rank [order [k ]]= (int32_t )k ; 449 } 450 451ranked = true; 452 453group_cells (); 454 } 455 456GGML_ASSERT ((!blk_bias || !oor )&& "qsa: cell position runs past the cell window" ); 457 458int32_t n_bid = 0 ; 459 460for (int64_t pb = 0 ;pb < n_blocks ;++ pb ) { 461for (int32_t g = grp_head [pb ];g >=0 ;g = grp_next [g ]) { 462if (grp_slots [g ]!= slots_full ) { 463continue ; 464 } 465 466grp_bid [g ]= n_bid ++ ; 467 468bid_idx .push_back ((int32_t ) (pb * r )); 469bid_cell .push_back (grp_first [g ]); 470bid_slot0 .push_back (grp_slot0 [g ]); 471 } 472 } 473 474GGML_ASSERT (n_bid <=n_blocks ); 475 476for (int32_t b = 0 ;b < n_bid ;++ b ) { 477int32_t sec_pos [4 ]= {bid_idx [b ],bid_idx [b ],bid_idx [b ],bid_idx [b ] }; 478 479if (ranked ) { 480const int32_t c = bid_slot0 [b ]; 481const llama_pos p = cells .pos_get (c ); 482const auto & e = cells .ext_get (c ); 483 484sec_pos [0 ]= p ; 485sec_pos [1 ]= e .y ; 486sec_pos [2 ]= e .x ; 487sec_pos [3 ]= p ; 488 } 489 490for (int64_t sec = 0 ;sec < 4 ;++ sec ) { 491dst_blk_pos [sec * (n_blocks * n_ns )+ s * n_blocks + b ]= sec_pos [sec ]; 492 } 493 } 494 495// unpooled cells all point at one spare block. a spare block exists only when some 496// cell is unpooled: n_bid == n_blocks means every cell sits in a full block. 497const bool have_dead = n_bid < n_blocks ; 498const int32_t dead_bid = have_dead ?n_bid :n_blocks - 1 ; 499 500for (int64_t j = 0 ;j < n_kv ;++ j ) { 501const int32_t g = cell_grp [j ]; 502 503blk_of [j ]= g < 0 ?-1 :grp_bid [g ]; 504 505if (blk_of [j ] >=0 ) { 506const int64_t idx = ranked ?rank [j ] :cells .pos_get (j ); 507 508cur_blk_cells [blk_of [j ]* r + (idx %r )]= (int32_t )j ; 509 } 510 511cur_cell_blk [j ]= blk_of [j ]< 0 ?dead_bid :blk_of [j ]; 512 } 513 514for (int64_t ii = 0 ;ii < n_tps ;++ ii ) { 515const int64_t i = s * n_tps + ii ; 516const llama_seq_id seq_id = ubatch -> seq_id [i ][0 ]; 517 518int64_t q = ubatch -> pos [i ]; 519 520if (ranked ) { 521const llama_pos qt = ubatch -> pos [i ]; 522const llama_pos qy = ubatch -> pos [i + n_tokens ]; 523const llama_pos qx = ubatch -> pos [i + n_tokens * 2 ]; 524 525int64_t lo = 0 ; 526int64_t hi = (int64_t )order .size (); 527 528while (lo < hi ) { 529const int64_t mid = (lo + hi )/2 ; 530const int32_t c = order [mid ]; 531const llama_pos pc = cells .pos_get (c ); 532 533if (pc < qt || (pc == qt && !cells .ext_get (c ).is_2d_gt (qx ,qy ))) { 534lo = mid + 1 ; 535 }else { 536hi = mid ; 537 } 538 } 539 540q = lo - 1 ; 541 } 542 543// the tail is an incomplete block and is always visible, as in the reference 544const int64_t tail_start = (q + 1 )/r * r ; 545 546if (blk_bias ) { 547// a block sits wholly inside or outside the tail, so one value covers it 548// the caller adds the attention mask, which drops empty, foreign and future cells 549float * cur_blk_bias = dst_bias + i * n_blocks ; 550 551for (int64_t b = 0 ;b < n_blocks ;++ b ) { 552if (b >=n_bid || !cells .seq_has ((uint32_t )bid_cell [b ],seq_id )) { 553cur_blk_bias [b ]= - INFINITY ; 554continue ; 555 } 556 557// finite, so it can never meet a -inf and produce a nan 558cur_blk_bias [b ]= bid_idx [b ] >=tail_start ?1e9f :0.0f ; 559 } 560 561// the spare block holds the unpooled cells, which are the incomplete tail, so 562// it gets the tail value. it must stay finite: a sequence with fewer than 563// `ratio` cells owns no full block, and a row of -inf only gives a nan. 564if (have_dead ) { 565cur_blk_bias [dead_bid ]= 1e9f ; 566 } 567 568continue ; 569 } 570 571float * cur_bias = dst_bias + i * n_kv ; 572 573for (int64_t j = 0 ;j < n_kv ;++ j ) { 574float v = - INFINITY ; 575 576if (!cells .is_empty (j )&& cells .seq_has (j ,seq_id )) { 577const int64_t idx = ranked ?rank [j ] :cells .pos_get (j ); 578 579if (idx <=q ) { 580// finite, so it can never meet a -inf and produce a nan 581v = idx >=tail_start ?1e9f : (blk_of [j ]< 0 ?- INFINITY :0.0f ); 582 } 583 } 584 585cur_bias [j ]= v ; 586 } 587 } 588 } 589} 590 591// 592// llama_memory_hybrid_idx_context 593// 594 595// streams in each ubatch's slot info, matching get_k/get_v's `ns` 596static std::vector < uint32_t > llama_memory_hybrid_idx_ns (const llama_kv_cache::slot_info_vec_t & sinfos ) { 597 std::vector < uint32_t > res ; 598res .reserve (sinfos .size ()); 599 600for (const auto & sinfo :sinfos ) { 601res .push_back (sinfo .s1 - sinfo .s0 + 1 ); 602 } 603 604return res ; 605} 606 607llama_memory_hybrid_idx_context::llama_memory_hybrid_idx_context (llama_memory_status status ) : 608llama_memory_hybrid_context (status ) {} 609 610llama_memory_hybrid_idx_context::llama_memory_hybrid_idx_context (llama_memory_hybrid_idx * mem ) : 611llama_memory_hybrid_context (mem ), 612mem (mem ), 613// graph reservation walks a full context, and qwen4exp builds the sparse attention only when this is set 614// without it the reserved worst case is the dense graph, so ggml-alloc must grow the buffer on the first decode 615ns_ubatch (mem -> get_mem_idx ()== nullptr ? 616 std::vector < uint32_t > () : std::vector < uint32_t > {mem -> get_mem_idx ()-> get_n_stream () }), 617ctx_idx (mem -> get_mem_idx ()== nullptr ?nullptr : 618new llama_kv_cache_context (mem -> get_mem_idx ())) {} 619 620llama_memory_hybrid_idx_context::llama_memory_hybrid_idx_context ( 621llama_memory_hybrid_idx * mem , 622llama_context * lctx , 623bool optimize ) : 624llama_memory_hybrid_context (mem ,lctx ,optimize ), 625mem (mem ), 626// update() applies a pending cross-stream seq_cp, else the copy keeps stale indexer keys 627ctx_idx (mem -> get_mem_idx ()== nullptr ?nullptr : 628mem -> get_mem_idx ()-> init_update (lctx ,optimize )) {} 629 630llama_memory_hybrid_idx_context::llama_memory_hybrid_idx_context ( 631llama_memory_hybrid_idx * mem , 632slot_info_vec_t sinfos_attn , 633slot_info_vec_t sinfos_idx , 634 std::vector < llama_ubatch > ubatches ) : 635// note: the base copies the ubatches; ctx_idx gets a copy of its own 636llama_memory_hybrid_context (mem , std::move (sinfos_attn ),ubatches ), 637mem (mem ), 638ns_ubatch (llama_memory_hybrid_idx_ns (sinfos_idx )), 639ctx_idx (mem -> get_mem_idx ()== nullptr ?nullptr : 640new llama_kv_cache_context (mem -> get_mem_idx (), std::move (sinfos_idx ),ubatches )) {} 641 642bool llama_memory_hybrid_idx_context::next () { 643if (ctx_idx ) { 644ctx_idx -> next (); 645 } 646 647++ i_cur ; 648 649return llama_memory_hybrid_context::next (); 650} 651 652bool llama_memory_hybrid_idx_context::apply () { 653bool res = llama_memory_hybrid_context::apply (); 654 655if (ctx_idx ) { 656res = res & ctx_idx -> apply (); 657 } 658 659return res ; 660} 661 662const llama_kv_cache_context * llama_memory_hybrid_idx_context::get_idx ()const { 663return static_cast < const llama_kv_cache_context *> (ctx_idx .get ()); 664} 665 666uint32_t llama_memory_hybrid_idx_context::get_n_stream ()const { 667GGML_ASSERT (i_cur < ns_ubatch .size ()); 668 669return ns_ubatch [i_cur ]; 670} 671 672void llama_memory_hybrid_idx_context::set_input_qsa ( 673ggml_tensor * cell_blk , 674ggml_tensor * blk_cells , 675ggml_tensor * blk_pos , 676ggml_tensor * bias , 677const llama_ubatch * ubatch , 678uint32_t ratio , 679bool blk_bias )const { 680GGML_ASSERT (mem != nullptr ); 681 682mem -> set_input_qsa (cell_blk ,blk_cells ,blk_pos ,bias ,ubatch ,ratio ,blk_bias ); 683}