@@ -155,7 +155,9 @@ static ggml_tensor * build_gemma4_attn_block(
155155 ggml_tensor * attn_mask_full,
156156 ggml_tensor * attn_mask_swa,
157157 int kv_start,
158- int n_tokens)
158+ int n_tokens,
159+ ggml_tensor * kv_idx_full = nullptr , // [n_tokens] I32 absolute rows (graph input)
160+ ggml_tensor * kv_idx_swa = nullptr ) // [n_tokens] I32 ring rows pos%swa_size (graph input)
159161{
160162 const int head_dim = gemma4_head_dim (w, il);
161163 const int n_head = w.n_head ;
@@ -207,20 +209,35 @@ static ggml_tensor * build_gemma4_attn_block(
207209 0 .0f , 1 .0f , 32 .0f , 1 .0f );
208210
209211 // Write K/V to cache (ring-buffer position for SWA layers)
210- const int write_pos = is_swa ? (kv_start % cache_len) : kv_start;
211212 ggml_tensor * Kcur_T = ggml_permute (ctx, Kcur, 0 , 2 , 1 , 3 );
212213 ggml_tensor * Vcur_T = ggml_permute (ctx, Vcur, 0 , 2 , 1 , 3 );
213214
214- ggml_tensor * k_slot = ggml_view_3d (ctx, cache_k,
215- head_dim, n_tokens, n_head_kv,
216- cache_k->nb [1 ], cache_k->nb [2 ],
217- cache_k->nb [1 ] * (size_t )write_pos);
218- ggml_tensor * v_slot = ggml_view_3d (ctx, cache_v,
219- head_dim, n_tokens, n_head_kv,
220- cache_v->nb [1 ], cache_v->nb [2 ],
221- cache_v->nb [1 ] * (size_t )write_pos);
222- ggml_build_forward_expand (gf, ggml_cpy (ctx, Kcur_T, k_slot));
223- ggml_build_forward_expand (gf, ggml_cpy (ctx, Vcur_T, v_slot));
215+ ggml_tensor * kvi = is_swa ? kv_idx_swa : kv_idx_full;
216+ if (kvi) {
217+ // CUDA-graph-stable append: dst is the whole cache tensor (stable
218+ // pointer), the row index is a graph INPUT (data changes per step,
219+ // pointer doesn't). A write_pos-offset view changes node properties
220+ // every step, which resets the ggml-cuda CUDA-graph warmup and
221+ // forfeits replay. For SWA layers the caller fills the index with
222+ // (pos % swa_size), which also handles ring wrap-around mid-chunk
223+ // correctly (the offset-view path wrote a contiguous block).
224+ ggml_tensor * Krows = ggml_cont (ctx, Kcur_T);
225+ ggml_tensor * Vrows = ggml_cont (ctx, Vcur_T);
226+ ggml_build_forward_expand (gf, ggml_set_rows (ctx, cache_k, Krows, kvi));
227+ ggml_build_forward_expand (gf, ggml_set_rows (ctx, cache_v, Vrows, kvi));
228+ } else {
229+ const int write_pos = is_swa ? (kv_start % cache_len) : kv_start;
230+ ggml_tensor * k_slot = ggml_view_3d (ctx, cache_k,
231+ head_dim, n_tokens, n_head_kv,
232+ cache_k->nb [1 ], cache_k->nb [2 ],
233+ cache_k->nb [1 ] * (size_t )write_pos);
234+ ggml_tensor * v_slot = ggml_view_3d (ctx, cache_v,
235+ head_dim, n_tokens, n_head_kv,
236+ cache_v->nb [1 ], cache_v->nb [2 ],
237+ cache_v->nb [1 ] * (size_t )write_pos);
238+ ggml_build_forward_expand (gf, ggml_cpy (ctx, Kcur_T, k_slot));
239+ ggml_build_forward_expand (gf, ggml_cpy (ctx, Vcur_T, v_slot));
240+ }
224241 }
225242 // else: KV-sharing layer — cache already written by source layer
226243
@@ -282,7 +299,9 @@ static ggml_tensor * build_gemma4_layer(
282299 ggml_tensor * per_layer_input, // [n_embd_per_layer, n_tokens] or nullptr
283300 int kv_start,
284301 int n_tokens,
285- int capture_idx = -1 ) // >=0: write to target_feat at this capture slot
302+ int capture_idx = -1 , // >=0: write to target_feat at this capture slot
303+ ggml_tensor * kv_idx_full = nullptr ,
304+ ggml_tensor * kv_idx_swa = nullptr )
286305{
287306 const Gemma4Layer & L = w.layers [il];
288307 ggml_tensor * inp_f32 = graph_tensor_f32 (ctx, inp);
@@ -293,7 +312,7 @@ static ggml_tensor * build_gemma4_layer(
293312 // Attention
294313 cur = build_gemma4_attn_block (ctx, gf, w, L, cache, il, cur,
295314 positions, attn_mask_full, attn_mask_swa,
296- kv_start, n_tokens);
315+ kv_start, n_tokens, kv_idx_full, kv_idx_swa );
297316
298317 // Post-attn norm
299318 if (L.attn_post_norm ) {
@@ -603,9 +622,16 @@ bool gemma4_step(
603622 int kv_start,
604623 std::vector<float > & out_logits)
605624{
606- // Allocate graph context
625+ // Allocate graph context. Persistent thread_local arena: rebuilt graphs
626+ // land at identical addresses every step, so the ggml-cuda CUDA-graph
627+ // cache (keyed on nodes[0], memcmps node properties) can replay the
628+ // captured graph instead of re-launching every kernel per token.
629+ const size_t arena_size = ggml_tensor_overhead () * 16384 + ggml_graph_overhead () + 16 * 1024 * 1024 ;
630+ static thread_local std::vector<uint8_t > g_arena;
631+ if (g_arena.size () < arena_size) g_arena.resize (arena_size);
607632 ggml_init_params ip{};
608- ip.mem_size = ggml_tensor_overhead () * 16384 + ggml_graph_overhead () + 16 * 1024 * 1024 ;
633+ ip.mem_size = arena_size;
634+ ip.mem_buffer = g_arena.data ();
609635 ip.no_alloc = true ;
610636 ggml_context * ctx = ggml_init (ip);
611637 ggml_cgraph * gf = ggml_new_graph_custom (ctx, 16384 , false );
@@ -616,6 +642,18 @@ bool gemma4_step(
616642 ggml_tensor * pp = ggml_new_tensor_1d (ctx, GGML_TYPE_I32 , n_tokens);
617643 ggml_set_input (pp);
618644
645+ // K/V append row indices (set_rows path; data-only per step -> stable
646+ // node properties -> CUDA-graph replay). DFLASH_GEMMA4_NO_KVPAD=1 restores
647+ // the legacy offset-view cpy append.
648+ static const bool g_no_kvpad = (std::getenv (" DFLASH_GEMMA4_NO_KVPAD" ) != nullptr );
649+ ggml_tensor * kvi_full = nullptr , * kvi_swa = nullptr ;
650+ if (!g_no_kvpad) {
651+ kvi_full = ggml_new_tensor_1d (ctx, GGML_TYPE_I32 , n_tokens);
652+ ggml_set_input (kvi_full);
653+ kvi_swa = ggml_new_tensor_1d (ctx, GGML_TYPE_I32 , n_tokens);
654+ ggml_set_input (kvi_swa);
655+ }
656+
619657 // Token IDs input (for per-layer embedding lookup)
620658 ggml_tensor * tok_ids = nullptr ;
621659 if (token_ids && w.per_layer_tok_embd && w.per_layer_model_proj && w.n_embd_per_layer > 0 ) {
@@ -689,7 +727,8 @@ bool gemma4_step(
689727 }
690728 cur = build_gemma4_layer (ctx, gf, w, cache, il, cur, pp,
691729 mk_full_f16, mk_swa_f16, pl_input,
692- kv_start, n_tokens, cap_idx);
730+ kv_start, n_tokens, cap_idx,
731+ kvi_full, kvi_swa);
693732 }
694733
695734 // Final norm
@@ -729,6 +768,17 @@ bool gemma4_step(
729768 std::vector<int32_t > pos ((size_t )n_tokens);
730769 for (int i = 0 ; i < n_tokens; ++i) pos[i] = kv_start + i;
731770 ggml_backend_tensor_set (pp, pos.data (), 0 , ggml_nbytes (pp));
771+ if (kvi_full) {
772+ // Full layers append at the absolute position; SWA layers at the ring
773+ // slot. Per-token modular indices also land chunks that cross the
774+ // ring wrap boundary correctly (the offset-view path wrote one
775+ // contiguous block).
776+ ggml_backend_tensor_set (kvi_full, pos.data (), 0 , ggml_nbytes (kvi_full));
777+ GGML_ASSERT (swa_size > 0 );
778+ std::vector<int32_t > ring ((size_t )n_tokens);
779+ for (int i = 0 ; i < n_tokens; ++i) ring[i] = (kv_start + i) % swa_size;
780+ ggml_backend_tensor_set (kvi_swa, ring.data (), 0 , ggml_nbytes (kvi_swa));
781+ }
732782
733783 // Set token IDs for per-layer embedding
734784 if (tok_ids && token_ids) {
0 commit comments