warmup.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463
  1. # SPDX-License-Identifier: Apache-2.0
  2. # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
  3. from collections.abc import Callable
  4. from contextlib import AbstractContextManager, nullcontext
  5. from typing import Any
  6. import numpy as np
  7. import torch
  8. from vllm import PoolingParams, SamplingParams
  9. from vllm.logger import init_logger
  10. from vllm.multimodal.inputs import MultiModalFeatureSpec, PlaceholderRange
  11. from vllm.utils.math_utils import cdiv
  12. from vllm.v1.core.sched.output import (
  13. CachedRequestData,
  14. GrammarOutput,
  15. NewRequestData,
  16. SchedulerOutput,
  17. )
  18. from vllm.v1.kv_cache_interface import (
  19. CircularBufferSpec,
  20. CrossAttentionSpec,
  21. KVCacheSpec,
  22. MambaSpec,
  23. UniformTypeKVCacheSpecs,
  24. )
  25. from vllm.v1.request import Request
  26. from vllm.v1.worker.gpu.model_runner import GPUModelRunner
  27. logger = init_logger(__name__)
  28. def _draft_ids(model_runner, group_id: int, ids: list[int]) -> list[int]:
  29. """GLM53-DRAFT-POOL: warmup block ids for a group on the drafter's own,
  30. smaller pool, wrapped into it (warmup K/V contents do not matter)."""
  31. cfg = model_runner.kv_cache_config
  32. n = getattr(cfg, "draft_num_blocks", None)
  33. if not n or not set(cfg.draft_layer_names).intersection(cfg.kv_cache_groups[group_id].layer_names):
  34. return ids
  35. return [1 + (i - 1) % (n - 1) for i in ids]
  36. def _reserved_block_count(
  37. num_tokens: int,
  38. kvcache_spec: KVCacheSpec,
  39. *,
  40. num_lookahead_tokens: int,
  41. max_model_len: int,
  42. max_encoder_len: int,
  43. ) -> int:
  44. """Number of blocks the scheduler would hold for a request of `num_tokens`.
  45. Warmup hand-builds its `SchedulerOutput`s, so it must reserve what
  46. `KVCacheManager.allocate_slots` reserves: the token range plus
  47. `num_lookahead_tokens`, where the speculator writes the KV of its drafts.
  48. """
  49. if isinstance(kvcache_spec, UniformTypeKVCacheSpecs):
  50. kvcache_spec = kvcache_spec.first_spec
  51. if isinstance(kvcache_spec, CircularBufferSpec):
  52. # Circular caches keep one physical ring block for the request lifetime.
  53. return 1
  54. if isinstance(kvcache_spec, CrossAttentionSpec):
  55. # Cross-attention blocks cover the encoder sequence only.
  56. return cdiv(max_encoder_len, kvcache_spec.block_size)
  57. num_speculative_blocks = 0
  58. if isinstance(kvcache_spec, MambaSpec):
  59. # MambaManager appends speculative running-state blocks in every cache
  60. # mode; align mode sizes from the uncapped, lookahead-free token range.
  61. num_speculative_blocks = kvcache_spec.num_speculative_blocks
  62. if kvcache_spec.mamba_cache_mode == "align":
  63. return cdiv(num_tokens, kvcache_spec.block_size) + num_speculative_blocks
  64. num_tokens = min(num_tokens + num_lookahead_tokens, max_model_len)
  65. return cdiv(num_tokens, kvcache_spec.block_size) + num_speculative_blocks
  66. def _warmup_block_counter(
  67. model_runner: GPUModelRunner,
  68. ) -> Callable[[int, KVCacheSpec], int]:
  69. """Bind `_reserved_block_count` to `model_runner`'s reservation policy."""
  70. num_lookahead_tokens = model_runner.vllm_config.num_lookahead_tokens
  71. max_model_len = model_runner.max_model_len
  72. max_encoder_len = getattr(model_runner.model_state, "max_encoder_len", 0)
  73. def block_count(num_tokens: int, kvcache_spec: KVCacheSpec) -> int:
  74. return _reserved_block_count(
  75. num_tokens,
  76. kvcache_spec,
  77. num_lookahead_tokens=num_lookahead_tokens,
  78. max_model_len=max_model_len,
  79. max_encoder_len=max_encoder_len,
  80. )
  81. return block_count
  82. def run_mixed_prefill_decode_warmup(
  83. model_runner: GPUModelRunner,
  84. worker_execute_model: Callable[[SchedulerOutput], Any],
  85. worker_sample_tokens: Callable[[GrammarOutput | None], Any],
  86. num_tokens: int,
  87. *,
  88. mixed_step_context: AbstractContextManager[object] | None = None,
  89. req_id_prefix: str = "_v2_mixed_warmup",
  90. ) -> bool:
  91. """Run a V2 mixed prefill+decode step through normal scheduler inputs."""
  92. if model_runner.is_pooling_model or model_runner.max_num_reqs < 2 or num_tokens < 3:
  93. return False
  94. decode_req_id = f"{req_id_prefix}_decode_"
  95. prefill_req_id = f"{req_id_prefix}_prefill_"
  96. decode_prompt_len = 2
  97. decode_scheduled_tokens = 1
  98. prefill_len = num_tokens - decode_scheduled_tokens
  99. decode_token_ids = list(range(decode_prompt_len))
  100. prefill_token_ids = list(range(prefill_len))
  101. kv_cache_groups = model_runner.kv_cache_config.kv_cache_groups
  102. num_kv_cache_groups = len(kv_cache_groups)
  103. block_count = _warmup_block_counter(model_runner)
  104. kv_cache_specs = [g.kv_cache_spec for g in kv_cache_groups]
  105. decode_prefill_block_counts = [
  106. block_count(decode_prompt_len, s) for s in kv_cache_specs
  107. ]
  108. decode_block_counts = [
  109. block_count(decode_prompt_len + decode_scheduled_tokens, s)
  110. for s in kv_cache_specs
  111. ]
  112. decode_block_deltas = [
  113. decode - prefill
  114. for decode, prefill in zip(decode_block_counts, decode_prefill_block_counts)
  115. ]
  116. prefill_block_counts = [block_count(prefill_len, s) for s in kv_cache_specs]
  117. required_blocks = sum(decode_block_counts) + sum(prefill_block_counts)
  118. if model_runner.kv_cache_config.num_blocks <= required_blocks:
  119. logger.warning(
  120. "Skipping V2 mixed prefill+decode warmup because only %d KV blocks "
  121. "are available for %d required warmup blocks.",
  122. model_runner.kv_cache_config.num_blocks,
  123. required_blocks,
  124. )
  125. return False
  126. next_block_id = 1
  127. def _alloc_blocks(num_blocks: int) -> list[int]:
  128. nonlocal next_block_id
  129. block_ids = list(range(next_block_id, next_block_id + num_blocks))
  130. next_block_id += num_blocks
  131. return block_ids
  132. sampling_params = SamplingParams(max_tokens=2, temperature=0.0)
  133. decode_prefill_output = SchedulerOutput.make_empty()
  134. decode_prefill_output.scheduled_new_reqs = [
  135. NewRequestData(
  136. req_id=decode_req_id,
  137. prompt_token_ids=decode_token_ids,
  138. mm_features=[],
  139. sampling_params=sampling_params,
  140. pooling_params=None,
  141. block_ids=tuple(_draft_ids(model_runner, g, _alloc_blocks(n)) for g, n in enumerate(decode_prefill_block_counts)),
  142. num_computed_tokens=0,
  143. lora_request=None,
  144. prefill_token_ids=decode_token_ids,
  145. ),
  146. ]
  147. decode_prefill_output.num_scheduled_tokens = {
  148. decode_req_id: decode_prompt_len,
  149. }
  150. decode_prefill_output.total_num_scheduled_tokens = decode_prompt_len
  151. decode_prefill_output.num_common_prefix_blocks = [0] * num_kv_cache_groups
  152. decode_new_blocks = tuple(_draft_ids(model_runner, g, _alloc_blocks(n)) for g, n in enumerate(decode_block_deltas))
  153. cached_decode_req = CachedRequestData.make_empty()
  154. cached_decode_req.req_ids = [decode_req_id]
  155. cached_decode_req.num_computed_tokens = [decode_prompt_len]
  156. cached_decode_req.num_output_tokens = [1]
  157. cached_decode_req.new_block_ids = [
  158. decode_new_blocks if any(decode_block_deltas) else None
  159. ]
  160. mixed_output = SchedulerOutput.make_empty()
  161. mixed_output.scheduled_cached_reqs = cached_decode_req
  162. mixed_output.scheduled_new_reqs = [
  163. NewRequestData(
  164. req_id=prefill_req_id,
  165. prompt_token_ids=prefill_token_ids,
  166. mm_features=[],
  167. sampling_params=sampling_params,
  168. pooling_params=None,
  169. block_ids=tuple(_draft_ids(model_runner, g, _alloc_blocks(n)) for g, n in enumerate(prefill_block_counts)),
  170. num_computed_tokens=0,
  171. lora_request=None,
  172. prefill_token_ids=prefill_token_ids,
  173. ),
  174. ]
  175. mixed_output.num_scheduled_tokens = {
  176. decode_req_id: decode_scheduled_tokens,
  177. prefill_req_id: prefill_len,
  178. }
  179. mixed_output.total_num_scheduled_tokens = num_tokens
  180. mixed_output.num_common_prefix_blocks = [0] * num_kv_cache_groups
  181. cleanup_output = SchedulerOutput.make_empty()
  182. cleanup_output.finished_req_ids = {decode_req_id, prefill_req_id}
  183. context = mixed_step_context or nullcontext()
  184. model_runner.kv_connector.set_disabled(True)
  185. try:
  186. worker_execute_model(decode_prefill_output)
  187. worker_sample_tokens(None)
  188. with context:
  189. worker_execute_model(mixed_output)
  190. worker_sample_tokens(None)
  191. worker_execute_model(cleanup_output)
  192. finally:
  193. model_runner.kv_connector.set_disabled(False)
  194. return True
  195. @torch.inference_mode()
  196. def warmup_kernels(
  197. model_runner: GPUModelRunner,
  198. worker_execute_model: Callable[[SchedulerOutput], Any],
  199. worker_sample_tokens: Callable[[GrammarOutput | None], Any],
  200. ) -> None:
  201. """Run scheduler-realistic prefill and decode steps to JIT compile kernels.
  202. We must call the provided worker's execute_model for pipeline parallel
  203. coordination.
  204. """
  205. # Adaptive costs are calibrated during capture, after this warmup. Exercise
  206. # fixed draft counts here, then restore the manager for capture and serving.
  207. adaptive_verification = model_runner.adaptive_verification
  208. model_runner.adaptive_verification = None
  209. rejection_sampler = model_runner.rejection_sampler
  210. adaptive_sampling = (
  211. rejection_sampler is not None and rejection_sampler.enable_adaptive_verification
  212. )
  213. if adaptive_sampling:
  214. assert rejection_sampler is not None
  215. rejection_sampler.enable_adaptive_verification = False
  216. try:
  217. _warmup_kernels(model_runner, worker_execute_model, worker_sample_tokens)
  218. finally:
  219. model_runner.adaptive_verification = adaptive_verification
  220. if adaptive_sampling:
  221. assert rejection_sampler is not None
  222. rejection_sampler.enable_adaptive_verification = True
  223. def _warmup_kernels(
  224. model_runner: GPUModelRunner,
  225. worker_execute_model: Callable[[SchedulerOutput], Any],
  226. worker_sample_tokens: Callable[[GrammarOutput | None], Any],
  227. ) -> None:
  228. if model_runner.vllm_config.is_mm_encoder_only:
  229. return
  230. num_spec_steps = model_runner.num_speculative_steps
  231. decode_query_len = model_runner.decode_query_len
  232. # Use decode_query_len + 1 tokens so the prefill batch's per-request query
  233. # length exceeds decode_query_len, preventing it from being misclassified as
  234. # a uniform decode batch.
  235. prompt_len = decode_query_len + 1
  236. prompt_token_ids = list(range(prompt_len))
  237. # Upper bound on the decode steps built in `decode_steps` below.
  238. num_decode_steps = 1
  239. if not model_runner.is_pooling_model:
  240. num_decode_steps = 5 if num_spec_steps > 0 else 3
  241. # Size the block allocation for the worst case: every request advancing
  242. # decode_query_len tokens on every decode step.
  243. decode_len = prompt_len + num_decode_steps * decode_query_len
  244. kv_cache_groups = model_runner.kv_cache_config.kv_cache_groups
  245. num_kv_cache_groups = len(kv_cache_groups)
  246. # Encoder-decoder models: give each warmup request a dummy encoder input so
  247. # cross-attention warms up over a realistic, non-empty key sequence.
  248. # The dummy mm_feature is registered in the encoder cache and only its encoder
  249. # length is read (not the inputs themselves); the encoder itself is not scheduled.
  250. max_encoder_len = getattr(model_runner.model_state, "max_encoder_len", 0)
  251. warmup_mm_features: list[MultiModalFeatureSpec] = []
  252. if model_runner.is_encoder_decoder and max_encoder_len:
  253. warmup_mm_features = [
  254. MultiModalFeatureSpec(
  255. data=None,
  256. modality="",
  257. identifier="_warmup_encoder",
  258. mm_position=PlaceholderRange(offset=0, length=max_encoder_len),
  259. )
  260. ]
  261. # Compute per-request block counts for each KV cache group.
  262. block_count = _warmup_block_counter(model_runner)
  263. kv_cache_specs = [g.kv_cache_spec for g in kv_cache_groups]
  264. prefill_block_counts = [block_count(prompt_len, s) for s in kv_cache_specs]
  265. decode_block_counts = [block_count(decode_len, s) for s in kv_cache_specs]
  266. max_blocks_per_req = sum(decode_block_counts)
  267. num_reqs = min(
  268. model_runner.scheduler_config.max_num_seqs,
  269. model_runner.scheduler_config.max_num_batched_tokens
  270. // max(prompt_len, decode_query_len),
  271. )
  272. if max_blocks_per_req > 0:
  273. # Reserve block 0 (null block) and ensure we have enough blocks.
  274. # Encoder-only models allocate no KV blocks, so this cap doesn't apply.
  275. num_reqs = min(
  276. num_reqs,
  277. max(1, (model_runner.kv_cache_config.num_blocks - 1) // max_blocks_per_req),
  278. )
  279. req_ids = [f"_warmup_{i}_" for i in range(num_reqs)]
  280. # SamplingParams exercising all sampling features.
  281. if model_runner.is_pooling_model:
  282. sampling_params = None
  283. pooling_task = model_runner.model_config.get_pooling_task(
  284. model_runner.get_supported_tasks()
  285. )
  286. pooling_params = PoolingParams(task=pooling_task)
  287. pooling_params.verify(model_runner.model_config)
  288. else:
  289. sampling_params = SamplingParams.for_sampler_warmup()
  290. pooling_params = None
  291. # Assign distinct block IDs per request per group. 0 null block, start from 1.
  292. next_block_id = 1
  293. def _alloc_blocks(num_blocks: int) -> list[int]:
  294. nonlocal next_block_id
  295. return list(range(next_block_id, next_block_id := next_block_id + num_blocks))
  296. # The KV-block zeroing kernel is driven by the scheduler's
  297. # new_block_ids_to_zero, so none of the steps below reach it.
  298. if model_runner.kv_block_zeroer is not None:
  299. model_runner.kv_block_zeroer.warmup(model_runner.kv_cache_config.num_blocks)
  300. # Step 1: Prefill all requests with 1 + decode_query_len prompt tokens each.
  301. new_reqs = [
  302. NewRequestData.from_request(
  303. Request(
  304. req_ids[i],
  305. prompt_token_ids,
  306. sampling_params,
  307. pooling_params,
  308. mm_features=warmup_mm_features,
  309. ),
  310. block_ids=tuple(_draft_ids(model_runner, g, _alloc_blocks(n)) for g, n in enumerate(prefill_block_counts)),
  311. prefill_token_ids=prompt_token_ids,
  312. )
  313. for i in range(num_reqs)
  314. ]
  315. prefill_output = SchedulerOutput.make_empty()
  316. prefill_output.scheduled_new_reqs = new_reqs
  317. prefill_output.num_scheduled_tokens = {rid: prompt_len for rid in req_ids}
  318. prefill_output.total_num_scheduled_tokens = prompt_len * num_reqs
  319. prefill_output.num_common_prefix_blocks = [0] * num_kv_cache_groups
  320. # Disable KV connector for warmup run.
  321. model_runner.kv_connector.set_disabled(True)
  322. worker_execute_model(prefill_output)
  323. if not model_runner.is_pooling_model:
  324. # Warm up sampler and perform a decode step for non-pooling models.
  325. grammar_output = None
  326. if model_runner.is_last_pp_rank:
  327. # Build a GrammarOutput to exercise the structured output bitmask
  328. # kernel during the prefill step.
  329. vocab_size = model_runner.model_config.get_vocab_size()
  330. bitmask_width = (vocab_size + 31) // 32
  331. grammar_bitmask = np.full(
  332. (len(req_ids), bitmask_width), fill_value=-1, dtype=np.int32
  333. )
  334. grammar_output = GrammarOutput(
  335. structured_output_request_ids=req_ids, grammar_bitmask=grammar_bitmask
  336. )
  337. worker_sample_tokens(grammar_output)
  338. # Per-request state carried across the decode steps.
  339. req_computed = [prompt_len] * num_reqs
  340. req_blocks = [list(prefill_block_counts) for _ in range(num_reqs)]
  341. def _run_decode_step(indices: list[int], spec_flags: list[bool]) -> None:
  342. """Decode `indices`, spec-decoding the ones flagged in `spec_flags`."""
  343. cached_req_data = CachedRequestData.make_empty()
  344. cached_req_data.req_ids = [req_ids[i] for i in indices]
  345. cached_req_data.num_computed_tokens = [req_computed[i] for i in indices]
  346. cached_req_data.num_output_tokens = [1] * len(indices)
  347. cached_req_data.new_block_ids = []
  348. step_num_scheduled_tokens: dict[str, int] = {}
  349. step_spec_tokens: dict[str, list[int]] = {}
  350. for i, use_spec in zip(indices, spec_flags):
  351. num_tokens = decode_query_len if use_spec else 1
  352. after = req_computed[i] + num_tokens
  353. deltas = [
  354. block_count(after, spec) - held
  355. for spec, held in zip(kv_cache_specs, req_blocks[i])
  356. ]
  357. cached_req_data.new_block_ids.append(
  358. tuple(_draft_ids(model_runner, g, _alloc_blocks(n)) for g, n in enumerate(deltas)) if any(deltas) else None
  359. )
  360. req_blocks[i] = [
  361. held + delta for held, delta in zip(req_blocks[i], deltas)
  362. ]
  363. step_num_scheduled_tokens[req_ids[i]] = num_tokens
  364. if use_spec:
  365. step_spec_tokens[req_ids[i]] = [0] * num_spec_steps
  366. decode_output = SchedulerOutput.make_empty()
  367. decode_output.scheduled_cached_reqs = cached_req_data
  368. decode_output.num_scheduled_tokens = step_num_scheduled_tokens
  369. decode_output.scheduled_spec_decode_tokens = step_spec_tokens
  370. decode_output.total_num_scheduled_tokens = sum(
  371. step_num_scheduled_tokens.values()
  372. )
  373. decode_output.num_common_prefix_blocks = [0] * num_kv_cache_groups
  374. worker_execute_model(decode_output)
  375. worker_sample_tokens(None)
  376. for i, use_spec in zip(indices, spec_flags):
  377. req_computed[i] += decode_query_len if use_spec else 1
  378. all_indices = list(range(num_reqs))
  379. use_spec_decode = num_spec_steps > 0
  380. # Decode steps to warm, as (request indices, per-request spec flag).
  381. # Under spec decoding the scheduler drops requests the drafter proposed
  382. # nothing for, so warm each batch shape with and without draft tokens.
  383. decode_steps: list[tuple[list[int], list[bool]]] = [
  384. (all_indices, [use_spec_decode] * num_reqs),
  385. ]
  386. if num_reqs >= 2:
  387. # Mixed spec / non-spec: GDN and KDA reclassify the non-spec decode
  388. # as a prefill and split the batch into spec/non-spec token indices.
  389. decode_steps.append(([0, 1], [use_spec_decode, False]))
  390. if use_spec_decode:
  391. # Exercise the model paths that split a batch by whether each
  392. # request received draft tokens.
  393. decode_steps.append(([0, 1], [False, False]))
  394. if num_reqs > 1:
  395. decode_steps.append(([0], [use_spec_decode]))
  396. if use_spec_decode:
  397. decode_steps.append(([0], [False]))
  398. elif use_spec_decode:
  399. decode_steps.append(([0], [False]))
  400. for step_indices, step_spec_flags in decode_steps:
  401. _run_decode_step(step_indices, step_spec_flags)
  402. # Clean up - process finish_req_ids.
  403. cleanup_output = SchedulerOutput.make_empty()
  404. cleanup_output.finished_req_ids = set(req_ids)
  405. worker_execute_model(cleanup_output)
  406. model_runner.kv_connector.set_disabled(False)
  407. torch.accelerator.synchronize()