3434from lightllm .models .mistral_mtp .model import MistralMTPModel
3535from lightllm .models .qwen3_moe_mtp .model import Qwen3MOEMTPModel
3636from lightllm .server .api_cli import make_argument_parser
37- from lightllm .server .router .model_infer .mode_backend .mtp_pre_process import (
38- prepare_mtp_prefill_inputs ,
39- )
4037from lightllm .utils .config_utils import auto_set_fused_shared_experts , get_dtype , get_vocab_size
4138from lightllm .utils .dist_utils import init_distributed_env
42- from lightllm .utils .envs_utils import set_env_start_args
39+ from lightllm .utils .envs_utils import get_env_start_args , set_env_start_args
4340
4441
4542DEFAULT_BATCH_SIZES = [2 , 8 , 16 , 32 , 64 , 128 ]
@@ -130,18 +127,6 @@ def empty_multimodal_params(batch_size: int) -> List[Dict]:
130127 return [{"images" : [], "audios" : []} for _ in range (batch_size )]
131128
132129
133- def mtp_prefill_chunk_size (batch_size : int , prompt_len : int , batch_max_tokens : int ) -> int :
134- """Bound each MTP setup prefill step by the production token budget."""
135- if batch_size <= 0 :
136- raise ValueError (f"batch_size must be positive, got { batch_size } " )
137- if batch_max_tokens < batch_size :
138- raise ValueError (
139- "MTP prefill requires at least one token per request in a step: "
140- f"batch_size={ batch_size } , batch_max_tokens={ batch_max_tokens } "
141- )
142- return max (1 , min (int (prompt_len ), int (batch_max_tokens ) // int (batch_size )))
143-
144-
145130class StaticBenchmarkExecutor :
146131 def __init__ (
147132 self ,
@@ -199,115 +184,53 @@ def _run_prefill_case(self, case: BenchmarkCase, warmup: bool) -> BenchmarkResul
199184
200185 def _run_decode_case (self , case : BenchmarkCase , warmup : bool ) -> BenchmarkResult :
201186 mtp_enabled = self ._mtp_enabled ()
202- token_rows = self .token_source .batch (case .batch_size , case .context_len ) if mtp_enabled else None
203187 measured_tokens = case .batch_size * case .output_len
204188 elapsed = 0.0
205- ttft_elapsed = 0.0
206- decode_step_count = 0
207189 iters = self ._case_iters (warmup )
208190
209191 for _ in range (iters ):
210192 self ._reset_model_cache ()
193+ candidate_width = self ._mtp_step_width () if mtp_enabled else 1
194+ req_idx , seq_len , next_ids = self ._materialize_context_for_decode (case , candidate_width )
211195 if mtp_enabled :
212- torch .cuda .synchronize ()
213- ttft_start = time .perf_counter ()
214- req_idx , seq_len , next_ids = self ._prefill_for_decode (case , token_rows , mtp_enabled )
215- torch .cuda .synchronize ()
216- ttft_elapsed += time .perf_counter () - ttft_start
217- step_elapsed , step_count = self ._run_mtp_decode_steps (
196+ elapsed += self ._run_mtp_decode_steps (
218197 case = case ,
219198 req_idx = req_idx ,
220199 seq_len = seq_len ,
221200 next_ids = next_ids ,
222201 )
223- elapsed += step_elapsed
224- decode_step_count += step_count
225202 else :
226- req_idx , seq_len , next_ids = self ._materialize_context_for_decode (case )
227203 elapsed += self ._run_plain_decode_steps (
228204 case = case ,
229205 req_idx = req_idx ,
230206 seq_len = seq_len ,
231207 next_ids = next_ids ,
232208 )
233- decode_step_count += case .output_len
234209
235210 self ._reset_model_cache ()
236- inter_token_latency_ms = elapsed * 1000.0 / max (1 , decode_step_count ) if iters > 0 else None
211+ # An MTP verification round may commit multiple tokens; ITL is per
212+ # logical output token so it remains comparable with plain decode.
213+ logical_output_tokens = case .output_len * iters
214+ inter_token_latency_ms = elapsed * 1000.0 / max (1 , logical_output_tokens ) if iters > 0 else None
237215 return self ._make_result (
238216 case ,
239217 elapsed ,
240218 measured_tokens ,
241219 warmup ,
242- ttft_elapsed_s = ttft_elapsed if mtp_enabled else None ,
243220 inter_token_latency_ms = inter_token_latency_ms ,
244221 )
245222
246- def _materialize_context_for_decode (self , case : BenchmarkCase ):
247- """Allocate historical KV slots so decode can be measured without prefill."""
223+ def _materialize_context_for_decode (self , case : BenchmarkCase , candidate_width : int = 1 ):
224+ """Allocate synthetic historical KV so decode throughput excludes prefill."""
248225 req_idx = self ._alloc_req_indexes (case .batch_size )
249226 self ._materialize_cached_prefix (req_idx , case .context_len )
250227 seq_len = cpu_i32_full ((case .batch_size ,), case .context_len )
251- next_ids = torch .from_numpy (np .ascontiguousarray (self .token_source .batch (case .batch_size , 1 ).reshape (- 1 ))).to (
252- torch .int64
253- )
254- return req_idx , seq_len , next_ids
255-
256- def _prefill_for_decode (self , case : BenchmarkCase , token_rows : np .ndarray , mtp_enabled : bool ):
257- req_idx = self ._alloc_req_indexes (case .batch_size )
258- chunk_size = (
259- mtp_prefill_chunk_size (case .batch_size , case .context_len , self .args .batch_max_tokens )
260- if mtp_enabled
261- else None
262- )
263- inputs = self ._build_prefill_inputs (
264- token_rows = token_rows ,
265- req_idx = req_idx ,
266- prompt_len = case .context_len ,
267- chunk_size = chunk_size ,
268- )
269- output = None
270- next_ids = None
271- for model_input in inputs :
272- output = self ._forward_prefill_input (model_input , allow_overlap = not mtp_enabled )
273- self ._touch_output (output )
274- next_ids = self ._argmax_ids (output .logits )
275- if mtp_enabled :
276- # Main and draft KV must advance together for every chunk.
277- next_ids = self ._fill_mtp_prefill_kv (case , model_input , output , next_ids )
278- assert output is not None
279- assert next_ids is not None
280-
281- seq_len = cpu_i32_full ((case .batch_size ,), case .context_len )
228+ tokens = np .ascontiguousarray (self .token_source .batch (case .batch_size , candidate_width ))
229+ next_ids = torch .from_numpy (tokens ).to (torch .int64 )
230+ if candidate_width == 1 :
231+ next_ids = next_ids .reshape (- 1 )
282232 return req_idx , seq_len , next_ids
283233
284- def _fill_mtp_prefill_kv (
285- self ,
286- case : BenchmarkCase ,
287- main_prefill_input : ModelInput ,
288- main_output : ModelOutput ,
289- first_next_ids : torch .Tensor ,
290- ):
291- draft_input = main_prefill_input
292- draft_output = main_output
293- current_next_ids = first_next_ids .cuda (non_blocking = True )
294- mtp_candidates = [current_next_ids .detach ().cpu ()]
295- for draft_index in range (self ._num_mtp_modules ()):
296- draft_input = prepare_mtp_prefill_inputs (
297- model_input = draft_input ,
298- b_next_token_ids = current_next_ids ,
299- mtp_draft_input_hiddens = draft_output .mtp_main_output_hiddens ,
300- )
301- draft_output = self .draft_models [draft_index ].forward (draft_input )
302- current_next_ids = self ._argmax_ids (draft_output .logits ).cuda (non_blocking = True )
303- mtp_candidates .append (current_next_ids .detach ().cpu ())
304-
305- step_width = self ._mtp_step_width ()
306- while len (mtp_candidates ) < step_width :
307- mtp_candidates .append (mtp_candidates [- 1 ])
308- next_ids = torch .stack (mtp_candidates [:step_width ], dim = 1 )
309- return next_ids
310-
311234 def _run_plain_decode_steps (
312235 self ,
313236 case : BenchmarkCase ,
@@ -343,13 +266,12 @@ def _run_mtp_decode_steps(
343266 req_idx : torch .Tensor ,
344267 seq_len : torch .Tensor ,
345268 next_ids : torch .Tensor ,
346- ) -> tuple :
269+ ) -> float :
347270 elapsed = 0.0
348- step_count = 0
349271 generated_len = 0
350272 step_width = self ._mtp_step_width ()
351273 base_req_idx , b_mtp_index = self ._build_mtp_decode_index_tensors (req_idx , step_width )
352- current_candidates = next_ids
274+ current_candidates = next_ids . cuda ( non_blocking = True )
353275
354276 while generated_len < case .output_len :
355277 accepted_width = self ._sample_mtp_accept_width (step_width , case .output_len - generated_len )
@@ -396,13 +318,11 @@ def _run_mtp_decode_steps(
396318 accepted_width = accepted_width ,
397319 )
398320 .detach ()
399- .cpu ()
400321 )
401322 seq_len += accepted_width
402323 generated_len += accepted_width
403- step_count += 1
404324
405- return elapsed , step_count
325+ return elapsed
406326
407327 def _run_mtp_draft_decode (
408328 self ,
@@ -413,7 +333,7 @@ def _run_mtp_draft_decode(
413333 ):
414334 draft_input = model_input
415335 draft_output = model_output
416- draft_next_ids = self . _argmax_ids (model_output .logits ). cuda ( non_blocking = True )
336+ draft_next_ids = torch . argmax (model_output .logits , dim = - 1 ). detach (). to ( torch . int64 )
417337 generated = [draft_next_ids .detach ()]
418338
419339 temporary_mem = None
@@ -429,7 +349,7 @@ def _run_mtp_draft_decode(
429349 draft_input .mtp_draft_input_hiddens = draft_output .mtp_main_output_hiddens
430350 draft_model = self .draft_models [step % self ._num_mtp_modules ()]
431351 draft_output = draft_model .forward (draft_input )
432- draft_next_ids = self . _argmax_ids (draft_output .logits ). cuda ( non_blocking = True )
352+ draft_next_ids = torch . argmax (draft_output .logits , dim = - 1 ). detach (). to ( torch . int64 )
433353 generated .append (draft_next_ids .detach ())
434354
435355 if self .args .mtp_mode .startswith ("eagle" ) and step + 1 < self .args .mtp_step :
@@ -567,7 +487,7 @@ def _make_decode_input(
567487 total_token_num = int (seq_len .sum ().item ()),
568488 max_q_seq_len = 1 ,
569489 max_kv_seq_len = max_kv_seq_len ,
570- input_ids = input_ids .to (torch .int64 ). cpu () ,
490+ input_ids = input_ids .to (torch .int64 ),
571491 b_req_idx = req_idx ,
572492 b_mtp_index = mtp_index ,
573493 b_seq_len = seq_len ,
@@ -1212,6 +1132,15 @@ def init_deferred_cudagraph(args: SimpleNamespace, cases: Sequence[BenchmarkCase
12121132 model_kvargs ["graph_max_batch_size" ] = graph_batch_size
12131133 model_kvargs ["disable_cudagraph" ] = False
12141134
1135+ decode_batch_sizes = {int (case .batch_size ) for case in cases if case .stage == "decode" }
1136+ if decode_batch_sizes == {graph_batch_size }:
1137+ # A single profile case only replays its exact MTP-expanded batch. Avoid
1138+ # spending startup time and graph memory on smaller batches it never uses.
1139+ args .graph_split_batch_size = 0
1140+ args .graph_grow_step_size = graph_batch_size
1141+ set_env_start_args (args )
1142+ get_env_start_args .cache_clear ()
1143+
12151144 if args .enable_decode_microbatch_overlap :
12161145 graph_batch_size //= 2
12171146 model .graph_max_batch_size = graph_batch_size * (int (args .mtp_step ) + 1 )
@@ -1369,13 +1298,6 @@ def _is_dp_group_leader(args: SimpleNamespace, rank_id: int) -> bool:
13691298 return rank_id % _dp_world_size (args ) == 0
13701299
13711300
1372- def _raw_decode_step_count (result : Dict ) -> int :
1373- inter_token_latency_ms = result .get ("inter_token_latency_ms" )
1374- if inter_token_latency_ms is None or inter_token_latency_ms <= 0 :
1375- return 0
1376- return max (1 , int (round (float (result ["elapsed_ms" ]) / float (inter_token_latency_ms ))))
1377-
1378-
13791301def aggregate_rank_results (args : SimpleNamespace , messages : Sequence [Dict ]) -> List [Dict ]:
13801302 """Aggregate rank-local measurements into one global result per case."""
13811303 by_case : Dict [int , List [Dict ]] = {}
@@ -1416,10 +1338,11 @@ def aggregate_rank_results(args: SimpleNamespace, messages: Sequence[Dict]) -> L
14161338 logical_tokens = batch_size * int (first ["context_len" ]) * iters
14171339 first ["logical_tps" ] = logical_tokens / elapsed_s if elapsed_s > 0 else 0.0
14181340
1419- slowest_item = max (rank_items , key = lambda item : float (item ["result" ]["elapsed_ms" ]))
1420- decode_step_count = _raw_decode_step_count (slowest_item ["result" ])
1421- if decode_step_count > 0 :
1422- first ["inter_token_latency_ms" ] = elapsed_ms / decode_step_count
1341+ if first ["stage" ] == "decode" and iters > 0 :
1342+ # DP latency follows the slowest rank but keeps the per-token
1343+ # denominator; MTP verification rounds are not output tokens.
1344+ logical_output_tokens = int (first ["output_len" ]) * iters
1345+ first ["inter_token_latency_ms" ] = elapsed_ms / max (1 , logical_output_tokens )
14231346
14241347 ttft_values = [
14251348 float (item ["result" ]["ttft_ms" ]) for item in rank_items if item ["result" ].get ("ttft_ms" ) is not None
0 commit comments