@@ -162,7 +162,11 @@ GraphPersistentParam::GraphPersistentParam(const ModelArgs& args,
162162 // only serves decode / spec-verify batches, so the relevant row upper bound
163163 // comes from decode graph capacity instead.
164164 const int64_t max_graph_tokens = get_decode_graph_token_capacity (options);
165- const int64_t max_seqs_per_batch = get_decode_graph_capacity (options);
165+ // Hybrid spec verify keeps sequence-scoped metadata, while non-hybrid MTP
166+ // expands every proposal token into its own decode metadata row.
167+ const int64_t metadata_capacity = is_hybrid_linear_attention
168+ ? get_decode_graph_capacity (options)
169+ : max_graph_tokens;
166170
167171 const int64_t max_seq_len = args_.max_position_embeddings ();
168172
@@ -182,18 +186,17 @@ GraphPersistentParam::GraphPersistentParam(const ModelArgs& args,
182186 persistent_new_cache_slots_default_ = torch::zeros (
183187 {max_tokens_per_batch}, torch::dtype (torch::kInt ).device (device));
184188 persistent_linear_state_indices_ = torch::zeros (
185- {max_seqs_per_batch }, torch::dtype (torch::kInt ).device (device));
189+ {metadata_capacity }, torch::dtype (torch::kInt ).device (device));
186190 persistent_num_accepted_tokens_ = torch::ones (
187- {max_seqs_per_batch }, torch::dtype (torch::kInt ).device (device));
191+ {metadata_capacity }, torch::dtype (torch::kInt ).device (device));
188192
189- // Sequence length tensors with max_seqs_per_batch
190- q_seq_lens_ = torch::zeros ({max_seqs_per_batch},
193+ q_seq_lens_ = torch::zeros ({metadata_capacity},
191194 torch::dtype (torch::kInt ).device (device));
192- kv_seq_lens_ = torch::zeros ({max_seqs_per_batch },
195+ kv_seq_lens_ = torch::zeros ({metadata_capacity },
193196 torch::dtype (torch::kInt ).device (device));
194- q_seq_lens_default_ = torch::ones ({max_seqs_per_batch },
197+ q_seq_lens_default_ = torch::ones ({metadata_capacity },
195198 torch::dtype (torch::kInt ).device (device));
196- kv_seq_lens_default_ = torch::ones ({max_seqs_per_batch },
199+ kv_seq_lens_default_ = torch::ones ({metadata_capacity },
197200 torch::dtype (torch::kInt ).device (device));
198201 expanded_kv_seq_lens_ = torch::zeros (
199202 {max_tokens_per_batch}, torch::dtype (torch::kInt ).device (device));
@@ -203,10 +206,10 @@ GraphPersistentParam::GraphPersistentParam(const ModelArgs& args,
203206 const int64_t max_block_table_len =
204207 (max_seq_len + block_size - 1 ) / block_size + 1 ;
205208 persistent_block_tables_ =
206- torch::zeros ({max_seqs_per_batch , max_block_table_len},
209+ torch::zeros ({metadata_capacity , max_block_table_len},
207210 torch::dtype (torch::kInt ).device (device));
208211 persistent_block_tables_default_ =
209- torch::zeros ({max_seqs_per_batch , max_block_table_len},
212+ torch::zeros ({metadata_capacity , max_block_table_len},
210213 torch::dtype (torch::kInt ).device (device));
211214 persistent_expanded_block_tables_ =
212215 torch::zeros ({max_tokens_per_batch, max_block_table_len},
@@ -243,8 +246,8 @@ GraphPersistentParam::GraphPersistentParam(const ModelArgs& args,
243246 }
244247
245248 q_cu_seq_lens_default_ = torch::zeros (
246- {max_seqs_per_batch + 1 }, torch::dtype (torch::kInt ).device (device));
247- q_cu_seq_lens_ = torch::zeros ({max_seqs_per_batch + 1 },
249+ {metadata_capacity + 1 }, torch::dtype (torch::kInt ).device (device));
250+ q_cu_seq_lens_ = torch::zeros ({metadata_capacity + 1 },
248251 torch::dtype (torch::kInt ).device (device));
249252
250253 // Pre-allocate persistent dp/cp ep padding buffers with maximum capacity.
@@ -782,9 +785,15 @@ std::optional<ModelInputParams> GraphPersistentParam::update(
782785
783786 if (persistent_block_tables_default_.defined () &&
784787 persistent_block_tables_default_.sizes () ==
785- persistent_block_tables_.sizes ()) {
786- persistent_block_tables_.copy_ (persistent_block_tables_default_,
787- /* non_blocking=*/ true );
788+ persistent_block_tables_.sizes () &&
789+ padded_batch_size > 0 ) {
790+ CHECK_LE (padded_batch_size, persistent_block_tables_.size (0 ))
791+ << " padded graph metadata rows exceed persistent block table capacity" ;
792+ persistent_block_tables_
793+ .slice (/* dim=*/ 0 , /* start=*/ 0 , /* end=*/ padded_batch_size)
794+ .copy_ (persistent_block_tables_default_.slice (
795+ /* dim=*/ 0 , /* start=*/ 0 , /* end=*/ padded_batch_size),
796+ /* non_blocking=*/ true );
788797 }
789798 if (actual_seq_len_rows > 0 &&
790799 params.attention .device .block_tables .defined () &&
0 commit comments