@@ -3163,3 +3163,85 @@ def test_prepare_swaps_and_restore_recovers(self):
31633163 assert meta .kv_cache_manager is original_kv_mgr
31643164 torch .testing .assert_close (meta .kv_cache_block_offsets , original_offsets )
31653165 torch .testing .assert_close (meta .host_kv_cache_block_offsets , original_host_offsets )
3166+
3167+
3168+ @pytest .mark .skipif (not has_deep_gemm (), reason = "DeepGEMM not available" )
3169+ @skip_pre_hopper
3170+ def test_topk_indices_buffer_cuda_graph ():
3171+ from tensorrt_llm ._torch .memory_buffer_utils import Buffers
3172+
3173+ batch_size , next_n = 4 , 3
3174+ head_dim , block_size = 128 , 64
3175+ index_topk = 2048
3176+ kv_len = 512
3177+ layer_idx = 0
3178+ num_tokens = batch_size * next_n
3179+
3180+ cache_manager , sparse_attn_config = create_dsa_cache_manager (
3181+ batch_size = batch_size ,
3182+ head_dim = head_dim ,
3183+ tokens_per_block = block_size ,
3184+ max_seq_len = kv_len ,
3185+ num_layers = 1 ,
3186+ index_topk = index_topk ,
3187+ )
3188+ indexer = create_indexer (sparse_attn_config , layer_idx = layer_idx )
3189+
3190+ request_ids = list (range (batch_size ))
3191+ kv_lens = torch .tensor ([kv_len ] * batch_size , dtype = torch .int32 )
3192+ cache_manager .add_dummy_requests (
3193+ request_ids = request_ids ,
3194+ token_nums = kv_lens .tolist (),
3195+ is_gen = False ,
3196+ prepare_resource = True ,
3197+ )
3198+
3199+ metadata = _create_mock_metadata (
3200+ request_ids ,
3201+ batch_size ,
3202+ num_contexts = 0 ,
3203+ num_generations = batch_size ,
3204+ seq_lens = torch .tensor ([next_n ] * batch_size , dtype = torch .int32 ),
3205+ kv_lens = kv_lens .clone (),
3206+ num_cached_tokens = [kv_len - next_n ] * batch_size ,
3207+ cache_manager = cache_manager ,
3208+ num_ctx_tokens = 0 ,
3209+ num_tokens = num_tokens ,
3210+ max_draft_tokens = next_n - 1 ,
3211+ index_topk = index_topk ,
3212+ enable_indexer_skip = True ,
3213+ )
3214+ assert metadata .skip_indexer_for_gen_reqs , (
3215+ "test setup must hit the skip-indexer path (kv_len <= index_topk)"
3216+ )
3217+
3218+ metadata .is_cuda_graph = True
3219+ metadata .cuda_graph_buffers = Buffers ()
3220+
3221+ hidden_states = torch .zeros ((num_tokens , 1 ), device = "cuda" , dtype = torch .bfloat16 )
3222+ dummy = torch .zeros ((num_tokens , 1 ), device = "cuda" , dtype = torch .bfloat16 )
3223+
3224+ def _run_indexer ():
3225+ return indexer .sparse_attn_indexer (
3226+ metadata ,
3227+ hidden_states ,
3228+ q_fp8 = dummy ,
3229+ k_fp8 = dummy ,
3230+ k_scale = dummy ,
3231+ weights = dummy ,
3232+ update_k_cache = False ,
3233+ )
3234+
3235+ out1 = _run_indexer ()
3236+ ptr1 = out1 .data_ptr ()
3237+ out2 = _run_indexer ()
3238+ ptr2 = out2 .data_ptr ()
3239+
3240+ assert ptr1 == ptr2 , (
3241+ f"indexer topk-output buffer address changed across calls "
3242+ f"({ ptr1 :#x} -> { ptr2 :#x} ); under CUDA-graph capture it must be a "
3243+ f"persistent reserved-arena buffer to avoid stale-pointer IMA"
3244+ )
3245+ assert "indexer_topk_out_buffer" in metadata .cuda_graph_buffers .buffers , (
3246+ "indexer topk-output buffer must be drawn from the cuda_graph_buffers arena"
3247+ )
0 commit comments