Files
vLLM/vllm/model_executor/warmup/kernel_warmup.py
T
+13 aeeb36b1f1 [New model] Kimi K3 (#50000)
Signed-off-by: zjy0516 <riverclouds.zhu@qq.com>
Signed-off-by: Isotr0py <Isotr0py@outlook.com>
Signed-off-by: Bugen Zhao <i@bugenzhao.com>
Signed-off-by: Jee Jee Li <jeejeelee@inferact.ai>
Signed-off-by: Yifan Qiao <yifanqiao@inferact.ai>
Signed-off-by: Yongye Zhu <zyy1102000@gmail.com>
Signed-off-by: wzhao18 <wzhao18.sz@gmail.com>
Co-authored-by: Isotr0py <Isotr0py@outlook.com>
Co-authored-by: Thien Tran <gau.nernst@yahoo.com.sg>
Co-authored-by: Bugen Zhao <i@bugenzhao.com>
Co-authored-by: Yongye Zhu <zyy1102000@gmail.com>
Co-authored-by: Ziming Huang <zelda.huanghuang@gmail.com>
Co-authored-by: Roger Wang <hey@rogerw.io>
Co-authored-by: Isotr0py <mozf@inferact.ai>
Co-authored-by: aoshen02 <aoshen02@users.noreply.github.com>
Co-authored-by: Woosuk Kwon <woosuk@inferact.ai>
Co-authored-by: Jee Jee Li <jeejeelee@inferact.ai>
Co-authored-by: aoshen02 <aoshen@inferact.ai>
Co-authored-by: Summer Yang <girasoleyang@gmail.com>
Co-authored-by: Kevin H. Luu <khluu000@gmail.com>
Co-authored-by: Bowen Wang <abmfy@icloud.com>
Co-authored-by: gnovack <novackgm@gmail.com>
Co-authored-by: Nick Hill <nickhill123@gmail.com>
Co-authored-by: Yifan Qiao <yifanqiao@inferact.ai>
Co-authored-by: xiaozhoupy <peiyuanzhou1994@gmail.com>
Co-authored-by: Roy Wang <yasong.wang@inferact.ai>
Co-authored-by: Jeff (Junze) Ma <93145857+majunze2001@users.noreply.github.com>
Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Wei Zhao <51183510+wzhao18@users.noreply.github.com>
2026-07-30 03:49:58 -07:00

314 lines
11 KiB
Python

# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
"""
Warmup kernels used during model execution.
This is useful specifically for JIT'ed kernels as we don't want JIT'ing to
happen during model execution.
"""
from typing import TYPE_CHECKING
import torch
import vllm.envs as envs
from vllm.logger import init_logger
from vllm.model_executor.warmup.cutedsl_warmup import cutedsl_warmup
from vllm.model_executor.warmup.deep_gemm_warmup import deep_gemm_warmup
from vllm.model_executor.warmup.deepseek_v4_mhc_warmup import (
deepseek_v4_mhc_warmup,
)
from vllm.model_executor.warmup.fa4_cutedsl_warmup import (
fa4_cutedsl_warmup,
)
from vllm.model_executor.warmup.flashinfer_autotune_cache import (
resolve_flashinfer_autotune_file,
write_flashinfer_autotune_cache,
)
from vllm.model_executor.warmup.flashinfer_sparse_mla_warmup import (
deepseek_v4_sparse_mla_attention_warmup,
flashinfer_sparse_mla_decode_autotune_warmup,
)
from vllm.model_executor.warmup.kimi_k3_triton_warmup import (
kimi_k3_triton_warmup,
)
from vllm.model_executor.warmup.qwen_triton_warmup import qwen_triton_warmup
from vllm.model_executor.warmup.sparse_mla_triton_warmup import (
sparse_mla_triton_warmup,
)
from vllm.model_executor.warmup.v1_block_table_warmup import (
warm_v1_block_table_kernels,
)
from vllm.platforms import current_platform
from vllm.utils.deep_gemm import is_deep_gemm_supported
from vllm.utils.flashinfer import has_flashinfer
if TYPE_CHECKING:
from vllm.v1.worker.gpu_model_runner import GPUModelRunner
from vllm.v1.worker.gpu_worker import Worker
logger = init_logger(__name__)
_LL_BF16_WARMUP_M_RANGE = range(1, 17)
def _ll_bf16_router_shapes_from_model(
model: torch.nn.Module,
) -> tuple[tuple[int, int], ...]:
from vllm.model_executor.layers.fused_moe.router.gate_linear import GateLinear
shapes: set[tuple[int, int]] = set()
for module in model.modules():
if not isinstance(module, GateLinear):
continue
weight = getattr(module, "weight", None)
if not isinstance(weight, torch.Tensor):
continue
if weight.dim() != 2 or weight.dtype != torch.bfloat16:
continue
n, k = weight.shape
if k % 8 == 0:
shapes.add((int(k), int(n)))
return tuple(sorted(shapes))
def _warmup_ll_bf16_router_gemm(model: torch.nn.Module) -> None:
from vllm.model_executor.kernels.linear.cute_dsl.ll_bf16 import (
is_available as is_ll_bf16_gemm_available,
)
from vllm.model_executor.kernels.linear.cute_dsl.ll_bf16 import (
ll_bf16_gemm_kernel,
)
if not is_ll_bf16_gemm_available():
return
shapes = _ll_bf16_router_shapes_from_model(model)
if not shapes:
logger.info(
"Skipping ll_bf16 router GEMM warmup: no bf16 GateLinear shapes found."
)
return
logger.info("Warming up ll_bf16 router GEMM kernels for shapes: %s.", shapes)
ll_bf16_gemm_kernel.warmup(
shapes=shapes,
m_values=_LL_BF16_WARMUP_M_RANGE,
)
def kernel_warmup(worker: "Worker", *, process_local_only: bool = False):
from vllm.model_executor.warmup.minimax_m3_msa_warmup import (
minimax_m3_msa_warmup,
)
if not worker.use_v2_model_runner:
# Pooling models do not use the generation slot-mapping path.
if not worker.model_runner.is_pooling_model:
warm_v1_block_table_kernels(worker.model_runner)
# The KV-block zeroing kernel is driven by the scheduler's
# `new_block_ids_to_zero`, so no dummy run ever reaches it.
zeroer = getattr(worker.model_runner, "_kv_block_zeroer", None)
if zeroer is not None:
zeroer.warmup(worker.model_runner.kv_cache_config.num_blocks)
qwen_triton_warmup(worker.model_runner, worker.vllm_config.model_config)
# DSv4 mHC TileLang kernels (hc_pre/hc_post/hc_head_op) run every decoder
# layer per token; warm them across token sizes first so the first real
# request doesn't pay JIT cost. No-op for non-DSv4 models (gated inside).
deepseek_v4_mhc_warmup(
worker.get_model(),
max_tokens=worker.scheduler_config.max_num_batched_tokens,
cudagraph_capture_sizes=(
worker.vllm_config.compilation_config.cudagraph_capture_sizes or []
),
)
# Run next so input-prep kernels JIT against pristine runner state.
if worker.vllm_config.kernel_config.enable_jit_warmup:
kimi_k3_triton_warmup(worker)
fa4_cutedsl_warmup(worker)
sparse_mla_triton_warmup(worker)
if current_platform.has_device_capability(90):
_warmup_ll_bf16_router_gemm(worker.get_model())
if worker.vllm_config.kernel_config.enable_cutedsl_warmup:
# TODO(roberto): Remove after registered CuTeDSL warmups are migrated
# to the shared JIT warmup infrastructure.
# https://github.com/vllm-project/vllm/pull/47451
cutedsl_warmup()
if process_local_only:
return
flashinfer_sparse_mla_decode_autotune_warmup(worker)
deepseek_v4_sparse_mla_attention_warmup(worker)
# Deep GEMM warmup
do_deep_gemm_warmup = (
envs.VLLM_USE_DEEP_GEMM
and is_deep_gemm_supported()
and envs.VLLM_DEEP_GEMM_WARMUP != "skip"
)
if do_deep_gemm_warmup:
model = worker.get_model()
max_tokens = worker.scheduler_config.max_num_batched_tokens
deep_gemm_warmup(model, max_tokens)
minimax_m3_msa_warmup(worker)
enable_flashinfer_autotune = (
worker.vllm_config.kernel_config.enable_flashinfer_autotune
)
# FlashInfer autotune for Hopper (SM 9.0) and Blackwell (SM 10.0) GPUs
if enable_flashinfer_autotune is False:
logger.info("Skipping FlashInfer autotune because it is disabled.")
elif has_flashinfer() and current_platform.has_device_capability(90):
flashinfer_autotune(worker.model_runner)
# FlashInfer attention warmup
# Only warmup if the model has FlashInfer attention groups
# and is not a pooling model
def _is_flashinfer_backend(backend):
try:
return backend.get_name() == "FLASHINFER"
except NotImplementedError:
return False
if (
not worker.model_runner.is_pooling_model
and worker.model_runner.attn_groups
# NOTE: This should be `any` instead of `all` but other hybrid attention
# backends don't support this dummy run. Once we remove
# `build_for_cudagraph_capture`, we can change it to `any`.
and all(
_is_flashinfer_backend(group.backend)
for groups in worker.model_runner.attn_groups
for group in groups
)
):
logger.info("Warming up FlashInfer attention.")
# Warmup with mixed batch containing both prefill and decode tokens
# This is to warm up both prefill and decode attention kernels
worker.model_runner._dummy_run(
num_tokens=16,
skip_eplb=True,
is_profile=True,
force_attention=True,
create_mixed_batch=True,
)
def _flashinfer_autotune_skip_ops(runner: "GPUModelRunner") -> set[str] | None:
if envs.VLLM_FLASHINFER_AUTOTUNE_SKIP_OPS is not None:
return set(envs.VLLM_FLASHINFER_AUTOTUNE_SKIP_OPS) or None
from vllm.model_executor.kernels.linear import (
FlashInferCuteDslNvFp4LinearKernel,
)
for module in runner.get_model().modules():
for holder_name in ("quant_method", "scheme"):
kernel = getattr(getattr(module, holder_name, None), "kernel", None)
# CuTe-DSL mm_fp4 tuning JIT-compiles every tactic and its
# fallback is already the heuristic; all mm_fp4 backends share
# the "fp4_gemm" op name, so skip only when cute-dsl is selected.
if isinstance(kernel, FlashInferCuteDslNvFp4LinearKernel):
return {"fp4_gemm"}
return None
def flashinfer_autotune(runner: "GPUModelRunner") -> None:
"""
Autotune FlashInfer operations.
FlashInfer have many implementations for the same operation,
autotuning runs benchmarks for each implementation and stores
the results. The results are cached transparently and
future calls to FlashInfer will use the best implementation.
Without autotuning, FlashInfer will rely on heuristics, which may
be significantly slower.
Tuning is performed only on rank 0. The resulting cache is broadcast
to every rank so all ranks dispatch the same kernel tactic.
"""
import vllm.utils.flashinfer as fi_utils
from vllm.distributed.parallel_state import get_world_group
autotune_kwargs: dict = {}
skip_ops = _flashinfer_autotune_skip_ops(runner)
if skip_ops:
logger.info(
"Skipping FlashInfer autotuning for ops %s",
sorted(skip_ops),
)
autotune_kwargs["skip_ops"] = skip_ops
use_persistent_cache = True
# When distributed, tune on every rank so the collectives stay synchronized.
if get_world_group().world_size > 1:
use_persistent_cache = False
if not use_persistent_cache:
with torch.inference_mode(), fi_utils.autotune(**autotune_kwargs):
runner._dummy_run(
num_tokens=runner.scheduler_config.max_num_batched_tokens,
skip_eplb=True,
is_profile=True,
)
get_world_group().barrier()
return
world = get_world_group()
is_leader = world.rank_in_group == 0
cache_path = resolve_flashinfer_autotune_file(runner)
if is_leader:
logger.info("Using FlashInfer autotune cache file: %s", cache_path)
# We skip EPLB here since we don't want to record dummy metrics.
# When autotuning with number of tokens m, flashinfer will autotune
# operations for all number of tokens up to m, so we only need to
# run with the max number of tokens.
dummy_run_kwargs = dict(
num_tokens=runner.scheduler_config.max_num_batched_tokens,
skip_eplb=True,
is_profile=True,
)
with torch.inference_mode():
if is_leader:
with fi_utils.autotune(
tune_mode=True, cache=str(cache_path), **autotune_kwargs
):
runner._dummy_run(**dummy_run_kwargs)
else:
runner._dummy_run(**dummy_run_kwargs)
# Broadcast autotune cache from rank 0 to all other ranks so every
# rank loads the same set of chosen tactics.
tune_results: bytes | None = None
if is_leader and cache_path.exists():
with open(cache_path, "rb") as f:
tune_results = f.read()
tune_results = world.broadcast_object(tune_results, src=0)
if tune_results is None:
logger.warning(
"No FlashInfer autotune cache entries found."
"Falling back to default tactics."
)
else:
write_flashinfer_autotune_cache(cache_path, tune_results)
world.barrier()
from flashinfer.autotuner import AutoTuner
AutoTuner.get().load_configs(str(cache_path))
logger.info(
"FlashInfer autotune cache loaded on rank %d from %s.",
world.rank_in_group,
cache_path,
)