[NPU] Upgrade to v0.16.0 (#1375)

Signed-off-by: gcanlin <canlinguosdu@gmail.com>
This commit is contained in:
Canlin Guo authored and GitHub committed 2026-02-26 14:26:50 +08:00
1 parent 1ae2e1e4c4
commit 22c8cde328
10 files changed
+951 -1271

No files matched your search

+3 -2
View File
@@ -7,8 +7,9 @@ WORKDIR ${APP_DIR}
COPY . .
# Install vllm-omni with dev dependencies
RUN pip install --no-cache-dir -e . --no-build-isolation
RUN sed -i -E 's/^([[:space:]]*)"fa3-fwd==0\.0\.1",/\1# "fa3-fwd==0.0.1",/' pyproject.toml \
&& sed -i -E 's/\bonnxruntime\b/onnxruntime-cann/g' pyproject.toml
RUN pip install -v -e .
ENV VLLM_WORKER_MULTIPROC_METHOD=spawn
+3 -2
View File
@@ -7,8 +7,9 @@ WORKDIR ${APP_DIR}
COPY . .
# Install vllm-omni with dev dependencies
RUN pip install --no-cache-dir -e . --no-build-isolation
RUN sed -i -E 's/^([[:space:]]*)"fa3-fwd==0\.0\.1",/\1# "fa3-fwd==0.0.1",/' pyproject.toml \
&& sed -i -E 's/\bonnxruntime\b/onnxruntime-cann/g' pyproject.toml
RUN pip install -v -e .
ENV VLLM_WORKER_MULTIPROC_METHOD=spawn
+6 -2
View File
@@ -24,6 +24,10 @@ vLLM-Omni supports NPU through the vLLM Ascend Plugin (vllm-ascend). This is a c
### Build wheel from source
=== "NPU"
=== "NPU release"
--8<-- "docs/getting_started/installation/npu/npu.inc.md:installation"
--8<-- "docs/getting_started/installation/npu/npu.inc.md:installation-release"
=== "NPU from main"
--8<-- "docs/getting_started/installation/npu/npu.inc.md:installation-main"
@@ -3,14 +3,11 @@
For detailed hardware and software requirements, please refer to the [vllm-ascend installation documentation](https://docs.vllm.ai/projects/ascend/en/latest/installation.html).
# --8<-- [end:requirements]
# --8<-- [start:installation]
# --8<-- [start:installation-release]
The recommended way to use vLLM-Omni on NPU is through the vllm-ascend pre-built Docker images:
```bash
# Update DEVICE according to your NPUs (/dev/davinci[0-7])
export DEVICE0=/dev/davinci0
export DEVICE1=/dev/davinci1
# Update the vllm-ascend image
# Atlas A2:
# export IMAGE=quay.io/ascend/vllm-ascend:v0.14.0rc1
@@ -20,8 +17,10 @@ export IMAGE=quay.io/ascend/vllm-ascend:v0.14.0rc1
docker run --rm \
--name vllm-omni-npu \
--shm-size=1g \
--device $DEVICE0 \
--device $DEVICE1 \
--device /dev/davinci0 \
--device /dev/davinci1 \
--device /dev/davinci2 \
--device /dev/davinci3 \
--device /dev/davinci_manager \
--device /dev/devmm_svm \
--device /dev/hisi_hdc \
@@ -39,8 +38,9 @@ cd /vllm-workspace
git clone -b v0.14.0 https://github.com/vllm-project/vllm-omni.git
cd vllm-omni
VLLM_OMNI_TARGET_DEVICE=npu pip install -v -e .
# OR pip install -v -e . --no-build-isolation
sed -i -E 's/^([[:space:]]*)"fa3-fwd==0\.0\.1",/\1# "fa3-fwd==0.0.1",/' pyproject.toml \
&& sed -i -E 's/\bonnxruntime\b/onnxruntime-cann/g' pyproject.toml
pip install -v -e .
export VLLM_WORKER_MULTIPROC_METHOD=spawn
```
@@ -50,11 +50,38 @@ For other installation methods (pip installation, building from source, custom D
We are keeping [issue #997](https://github.com/vllm-project/vllm-omni/issues/997) up to date with the aligned versions of vLLM, vLLM-Ascend, and vLLM-Omni, and also outlining the Q1 roadmap there.
# --8<-- [end:installation]
# --8<-- [end:installation-release]
# --8<-- [start:installation-main]
You can also build vLLM-Omni from the latest main branch if you want to use the latest features or bug fixes. (But sometimes it will break for a while. You can check [issue #997](https://github.com/vllm-project/vllm-omni/issues/997) for the status of the latest commit of vLLM-Omni main branch on NPU.)
```bash
# Pin vLLM version to 0.16.0
cd /vllm-workspace/vllm
git pull origin main
git fetch origin --tags
git checkout v0.16.0
# Because vllm-ascend has not yet entered continuous development and has not been officially released, we need to pin it to a specific commit. Please note that this commit may change over time.
cd ../vllm-ascend
git pull origin main
git fetch origin --tags
git checkout e2175d9c7e62b437391dfee996b1375674ba7c18
# Install vLLM-Omni from the latest main branch
cd ../vllm-omni
git clone https://github.com/vllm-project/vllm-omni.git
pip install -v -e . --no-build-isolation
# or VLLM_OMNI_TARGET_DEVICE=npu pip install -v -e .
export VLLM_WORKER_MULTIPROC_METHOD=spawn
```
# --8<-- [end:installation-main]
# --8<-- [start:pre-built-images]
`vllm-ascend` offers Docker images for deployment. You can just pull the **prebuilt image** from the image repository [ascend/vllm-ascend](https://quay.io/repository/ascend/vllm-ascend?tab=tags) and run it with bash.
vLLM-Omni offers Docker images for Ascend NPU deployment. You can just pull the **prebuilt image** from the image repository [ascend/vllm-omni](https://quay.io/repository/ascend/vllm-omni?tab=tags) and run it with bash.
Supported images as following.
@@ -63,9 +90,7 @@ Supported images as following.
| image-tag | Atlas A2 | Ubuntu |
| image-tag-a3 | Atlas A3 | Ubuntu |
vLLM-Omni offers Docker images for Ascend NPU deployment. You can just pull the **prebuilt image** from the image repository [ascend/vllm-ascend](https://quay.io/repository/ascend/vllm-ascend?tab=tags) and run it with bash.
Here's an example deployment command that has been verified on 6 x NPUs:
Here's an example deployment command that has been verified on 4 x NPUs:
```bash
# Atlas A2:
@@ -80,8 +105,6 @@ docker run --rm \
--device /dev/davinci1 \
--device /dev/davinci2 \
--device /dev/davinci3 \
--device /dev/davinci4 \
--device /dev/davinci5 \
--device /dev/davinci_manager \
--device /dev/devmm_svm \
--device /dev/hisi_hdc \
@@ -37,7 +37,6 @@ from vllm_omni.model_executor.models.qwen3_omni.qwen3_omni_moe_thinker import (
Qwen3OmniMoeThinkerProcessingInfo,
)
from vllm_omni.model_executor.models.utils import add_prefix_to_loaded_weights, safe_tensor_reshape
from vllm_omni.platforms import current_omni_platform
# Special token IDs for Qwen3 Omni MoE
# Reference: https://huggingface.co/Qwen/Qwen3-Omni-30B-A3B-Instruct/blob/main/tokenizer_config.json
@@ -271,27 +270,6 @@ class Qwen3OmniMoeForConditionalGeneration(
# ========== Stage 1: Thinker ==========
if self.model_stage == "thinker":
thinker_dev = self._module_device(self.thinker)
if current_omni_platform.is_npu():
# Normalize to batched inputs if needed
_added_batch_dim = False
if input_ids is not None and input_ids.ndim == 1:
input_ids = input_ids.unsqueeze(0)
_added_batch_dim = True
if positions is not None and positions.ndim == 1:
positions = positions.unsqueeze(0)
_added_batch_dim = True
if inputs_embeds is not None and inputs_embeds.ndim == 2:
inputs_embeds = inputs_embeds.unsqueeze(0)
_added_batch_dim = True
# Handle None input_ids
if input_ids is None:
input_ids = torch.zeros(
inputs_embeds.shape[1],
dtype=torch.long,
device=thinker_dev,
).unsqueeze(0)
_added_batch_dim = True
# Move to thinker device
if input_ids is not None and input_ids.device != thinker_dev:
@@ -310,22 +288,13 @@ class Qwen3OmniMoeForConditionalGeneration(
"capture_layer_indices": [0, int(accept_layer)],
"return_hidden_states": True,
}
if current_omni_platform.is_npu():
# TODO: remove this hack when NPU supports batched inputs properly
thinker_input_ids = input_ids[0] if input_ids is not None and _added_batch_dim else input_ids
thinker_inputs_embeds = (
inputs_embeds[0] if inputs_embeds is not None and _added_batch_dim else inputs_embeds
)
else:
thinker_input_ids = input_ids
thinker_inputs_embeds = inputs_embeds
# Run thinker
text_hidden_states, captured_layer_dict = self.thinker(
input_ids=thinker_input_ids,
input_ids=input_ids,
positions=positions,
intermediate_tensors=intermediate_tensors,
inputs_embeds=thinker_inputs_embeds,
inputs_embeds=inputs_embeds,
**capture_kwargs,
**kwargs,
)
@@ -99,8 +99,8 @@ class Qwen3OmniMoeTalkerForConditionalGeneration(
def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
super().__init__()
talker_config: Qwen3OmniMoeTalkerConfig = vllm_config.model_config.hf_config
# rope_scaling already contains rope_theta, just assign it to rope_parameters
talker_config.text_config.rope_parameters = talker_config.text_config.rope_scaling
talker_config.text_config.rope_parameters["rope_theta"] = talker_config.text_config.rope_theta
self.quant_config = vllm_config.quant_config
self.prefix = prefix
self.vllm_config = vllm_config
@@ -7,7 +7,7 @@
stage_args:
- stage_id: 0
runtime:
devices: "0,1,2,3"
devices: "0,1"
max_batch_size: 1
engine_args:
model_stage: thinker
@@ -21,7 +21,7 @@ stage_args:
distributed_executor_backend: "mp"
enable_prefix_caching: false
hf_config_name: thinker_config
tensor_parallel_size: 4
tensor_parallel_size: 2
final_output: true
final_output_type: text
is_comprehension: true
@@ -36,14 +36,14 @@ stage_args:
- stage_id: 1
runtime:
devices: "4"
devices: "2"
max_batch_size: 1
engine_args:
model_stage: talker
model_arch: Qwen3OmniMoeForConditionalGeneration
worker_type: ar
scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler
gpu_memory_utilization: 0.2
gpu_memory_utilization: 0.6
enforce_eager: true # haven't supported talker ACL graph on NPU
trust_remote_code: true
engine_output_type: latent # Output codec codes for code2wav
@@ -66,7 +66,7 @@ stage_args:
- stage_id: 2
runtime:
devices: "0"
devices: "2"
max_batch_size: 1
engine_args:
model_stage: code2wav
@@ -78,7 +78,7 @@ stage_args:
async_scheduling: false
enable_prefix_caching: false
engine_output_type: audio # Final output: audio waveform
gpu_memory_utilization: 0.1
gpu_memory_utilization: 0.3
distributed_executor_backend: "mp"
max_num_batched_tokens: 1000000
hf_config_name: thinker_config
@@ -3,16 +3,16 @@
from __future__ import annotations
from copy import copy
from copy import copy, deepcopy
from typing import Any, NamedTuple
import numpy as np
import torch
from vllm.compilation.cuda_graph import CUDAGraphStat
from vllm.config import CUDAGraphMode
from vllm.distributed.ec_transfer import get_ec_transfer, has_ec_transfer
from vllm.distributed.kv_transfer import get_kv_transfer_group, has_kv_transfer_group
from vllm.distributed.parallel_state import get_pp_group, get_tp_group
from vllm.forward_context import get_forward_context
from vllm.logger import logger
from vllm.sequence import IntermediateTensors
from vllm.v1.core.sched.output import GrammarOutput, SchedulerOutput
@@ -24,16 +24,16 @@ from vllm.v1.outputs import (
)
from vllm.v1.spec_decode.metadata import SpecDecodeMetadata
from vllm.v1.structured_output.utils import apply_grammar_bitmask
from vllm.v1.worker.gpu_model_runner import AsyncGPUModelRunnerOutput
from vllm.v1.worker.kv_connector_model_runner_mixin import KVConnectorOutput
from vllm.v1.utils import record_function_or_nullcontext
from vllm.v1.worker.gpu_model_runner import AsyncGPUModelRunnerOutput, PerLayerAttnMetadata
from vllm.v1.worker.ubatch_utils import maybe_create_ubatch_slices
from vllm_ascend.ascend_forward_context import set_ascend_forward_context
from vllm_ascend.attention.attention_v1 import AscendAttentionState
from vllm_ascend.attention.utils import AscendCommonAttentionMetadata
# yapf conflicts with isort for this block
# yapf: disable
from vllm_ascend.compilation.acl_graph import update_full_graph_params
from vllm_ascend.ops.rotary_embedding import update_cos_sin
from vllm_ascend.utils import ProfileExecuteDuration
from vllm_ascend.utils import enable_sp
from vllm_omni.distributed.omni_connectors.kv_transfer_manager import OmniKVTransferManager
from vllm_omni.outputs import OmniModelRunnerOutput
@@ -47,13 +47,14 @@ class ExecuteModelState(NamedTuple):
scheduler_output: SchedulerOutput
logits: torch.Tensor
spec_decode_metadata: SpecDecodeMetadata | None
spec_decode_common_attn_metadata: AscendCommonAttentionMetadata | None
hidden_states: torch.Tensor
sample_hidden_states: torch.Tensor
aux_hidden_states: list[torch.Tensor] | None
kv_connector_output: KVConnectorOutput | None
attn_metadata: dict[str, Any]
attn_metadata: PerLayerAttnMetadata
positions: torch.Tensor
ec_connector_output: ECConnectorOutput | None
cudagraph_stats: CUDAGraphStat | None
multimodal_outputs: Any
class NPUARModelRunner(OmniNPUModelRunner):
@@ -86,10 +87,9 @@ class NPUARModelRunner(OmniNPUModelRunner):
self,
scheduler_output: SchedulerOutput,
intermediate_tensors: IntermediateTensors | None = None,
) -> OmniModelRunnerOutput | AsyncModelRunnerOutput | IntermediateTensors | None:
) -> OmniModelRunnerOutput | IntermediateTensors | None:
if self.execute_model_state is not None:
raise RuntimeError("State error: sample_tokens() must be called "
"after execute_model() returns None.")
raise RuntimeError("State error: sample_tokens() must be called after execute_model() returns None.")
# -------------------------------------- Omni-new -------------------------------------------------
# [Omni] Handle KV transfer BEFORE updating states (which removes finished requests)
@@ -101,110 +101,202 @@ class NPUARModelRunner(OmniNPUModelRunner):
request_id_resolver=self._resolve_global_request_id,
)
# -------------------------------------- Omni-new -------------------------------------------------
# self._draft_token_ids is None when `input_fits_in_drafter=False`
# and there is no draft tokens scheduled. so it need to update the
# spec_decoding info in scheduler_output with async_scheduling.
# use deepcopy to avoid the modification has influence on the
# scheduler_output in engine core process.
# TODO(Ronald1995): deepcopy is expensive when there is a large
# number of requests, optimize it later.
if (
self.use_async_scheduling and self.num_spec_tokens and self._draft_token_ids is None # type: ignore[has-type]
):
scheduler_output = deepcopy(scheduler_output)
num_scheduled_tokens = scheduler_output.total_num_scheduled_tokens
with record_function_or_nullcontext("prepare input"):
with self.synchronize_input_prep():
# -------------------------------------- Omni-new -------------------------------------------------
self._update_states(scheduler_output)
# ------------------------------------------------------------------------------------------------
# Update persistent batch states.
with ProfileExecuteDuration().capture_async("prepare input"):
# -------------------------------------- Omni-new -------------------------------------------------
self._update_states(scheduler_output)
self._decode_and_store_request_payloads(scheduler_output)
# ------------------------------------------------------------------------------------------------
if has_ec_transfer() and get_ec_transfer().is_producer:
with self.maybe_get_ec_connector_output(
if has_ec_transfer() and get_ec_transfer().is_producer:
with self.maybe_get_ec_connector_output(
scheduler_output,
encoder_cache=self.encoder_cache,
) as ec_connector_output:
self._execute_mm_encoder(scheduler_output)
return make_empty_encoder_model_runner_output(
scheduler_output)
) as ec_connector_output:
self._execute_mm_encoder(scheduler_output)
return make_empty_encoder_model_runner_output(scheduler_output)
if not scheduler_output.total_num_scheduled_tokens:
if not has_kv_transfer_group():
logger.debug(
"skip this step for we receive the data from remote disaggregate prefill node"
if not num_scheduled_tokens:
if (
self.parallel_config.distributed_executor_backend == "external_launcher"
and self.parallel_config.data_parallel_size > 1
):
# this is a corner case when both external launcher
# and DP are enabled, num_scheduled_tokens could be
# 0, and has_unfinished_requests in the outer loop
# returns True. before returning early here we call
# dummy run to ensure coordinate_batch_across_dp
# is called into to avoid out of sync issues.
self._dummy_run(1)
if not has_kv_transfer_group():
# Return empty ModelRunnerOutput if no work to do.
return EMPTY_MODEL_RUNNER_OUTPUT
return self.kv_connector_no_forward(scheduler_output, self.vllm_config)
if self.cache_config.kv_sharing_fast_prefill:
assert not self.num_prompt_logprobs, (
"--kv-sharing-fast-prefill produces incorrect "
"logprobs for prompt tokens, tokens, please disable "
"it when the requests need prompt logprobs"
)
# Return empty ModelRunnerOutput if there's no work to do.
return EMPTY_MODEL_RUNNER_OUTPUT
return self.kv_connector_no_forward(scheduler_output,
self.vllm_config)
num_reqs = self.input_batch.num_reqs
req_ids = self.input_batch.req_ids
tokens = [scheduler_output.num_scheduled_tokens[i] for i in req_ids]
num_scheduled_tokens_np = np.array(tokens, dtype=np.int32)
max_num_scheduled_tokens = int(num_scheduled_tokens_np.max())
if self.dynamic_eplb:
self.eplb_updator.forward_before()
(
logits_indices,
spec_decode_metadata,
) = self._prepare_inputs(
scheduler_output,
num_scheduled_tokens_np,
)
(attn_metadata, num_scheduled_tokens_np, num_input_tokens,
num_tokens_across_dp, logits_indices, spec_decode_metadata,
max_query_len) = self._prepare_inputs(scheduler_output)
num_tokens_unpadded = scheduler_output.total_num_scheduled_tokens
if self.pcp_size > 1:
num_tokens_unpadded = self.pcp_manager.total_num_sampled_tokens_pcp
cascade_attn_prefix_lens = None
# Disable cascade attention when using microbatching (DBO)
if self.cascade_attn_enabled and not self.parallel_config.enable_dbo:
# Pre-compute cascade attention prefix lengths
cascade_attn_prefix_lens = self._compute_cascade_attn_prefix_lens(
num_scheduled_tokens_np,
self.input_batch.num_computed_tokens_cpu[:num_reqs],
scheduler_output.num_common_prefix_blocks,
)
(input_ids, inputs_embeds, positions, intermediate_tensors,
model_kwargs, ec_connector_output) = self._preprocess(scheduler_output,
num_input_tokens,
intermediate_tensors)
(
cudagraph_mode,
batch_desc,
should_ubatch,
num_tokens_across_dp,
cudagraph_stats,
) = self._determine_batch_execution_and_padding(
num_tokens=num_tokens_unpadded,
num_reqs=num_reqs,
num_scheduled_tokens_np=num_scheduled_tokens_np,
max_num_scheduled_tokens=max_num_scheduled_tokens,
use_cascade_attn=cascade_attn_prefix_lens is not None,
num_encoder_reqs=len(scheduler_output.scheduled_encoder_inputs),
)
logger.debug(
"Running batch with cudagraph_mode: %s, batch_descriptor: %s, "
"should_ubatch: %s, num_tokens_across_dp: %s",
cudagraph_mode,
batch_desc,
should_ubatch,
num_tokens_across_dp,
)
num_tokens_padded = batch_desc.num_tokens
num_reqs_padded = batch_desc.num_reqs if batch_desc.num_reqs is not None else num_reqs
ubatch_slices, ubatch_slices_padded = maybe_create_ubatch_slices(
should_ubatch,
num_scheduled_tokens_np,
num_tokens_padded,
num_reqs_padded,
self.parallel_config.num_ubatches,
)
pad_attn = cudagraph_mode == CUDAGraphMode.FULL
use_spec_decode = len(scheduler_output.scheduled_spec_decode_tokens) > 0
ubatch_slices_attn = ubatch_slices_padded if pad_attn else ubatch_slices
if (
cudagraph_mode == CUDAGraphMode.FULL
or (enable_sp() and not self.model_config.use_mla)
and self.pcp_size == 1 # TODO(lxs): fix this
):
# Currently, Graph Mode and SP will both pad num_tokens,
# Another possible condition is num_tokens_padded != num_tokens_unpadded
# but this scope is way too big and the consequences are unpredictable
num_reqs_padded = self._pad_query_start_loc_for_fia(num_tokens_padded, num_reqs_padded, num_reqs)
(attn_metadata, spec_decode_common_attn_metadata) = self._build_attention_metadata(
num_tokens=num_tokens_unpadded,
num_tokens_padded=num_tokens_padded,
num_reqs=num_reqs,
num_reqs_padded=num_reqs_padded,
max_query_len=max_num_scheduled_tokens,
ubatch_slices=ubatch_slices_attn,
logits_indices=logits_indices,
use_spec_decode=use_spec_decode,
num_scheduled_tokens=scheduler_output.num_scheduled_tokens,
num_scheduled_tokens_np=num_scheduled_tokens_np,
cascade_attn_prefix_lens=cascade_attn_prefix_lens,
)
(
input_ids,
inputs_embeds,
positions,
intermediate_tensors,
model_kwargs,
ec_connector_output,
) = self._preprocess(scheduler_output, num_tokens_padded, intermediate_tensors)
# update global cos, sin
update_cos_sin(positions)
if self.dynamic_eplb:
self.eplb_updator.take_update_info_from_eplb_process()
# Set cudagraph mode to none if calc_kv_scales is true.
# KV scales calculation involves dynamic operations that are incompatible
# with CUDA graph capture.
if self.calculate_kv_scales: # type: ignore[has-type]
cudagraph_mode = CUDAGraphMode.NONE
# Mark KV scales as calculated after the first forward pass
self.calculate_kv_scales = False # type: ignore[has-type]
# prevent debugger is None
if self.debugger is not None:
dbg_cfg = getattr(self.debugger, "config", None)
dump_level = str(
getattr(dbg_cfg, "level",
"L1")).upper() if dbg_cfg is not None else "L1"
dump_level = str(getattr(dbg_cfg, "level", "L1")).upper() if dbg_cfg is not None else "L1"
if dump_level in ("L0", "MIX"):
self.debugger.start(model=self.model)
else:
self.debugger.start()
uniform_decode = (max_query_len == self.uniform_decode_query_len) and (
scheduler_output.total_num_scheduled_tokens
== self.input_batch.num_reqs * max_query_len)
has_lora = len(self.input_batch.lora_id_to_lora_request) > 0
aclgraph_runtime_mode, batch_descriptor = \
self.cudagraph_dispatcher.dispatch(
num_tokens=num_input_tokens,
uniform_decode=uniform_decode,
has_lora=has_lora
)
if self.ascend_config.enable_async_exponential:
self.sampler.do_async_exponential(
b_s=logits_indices.shape[0],
head_dim=self.model_config.get_vocab_size(),
generators=self.input_batch.sampling_metadata.generators)
generators=self.input_batch.sampling_metadata.generators,
)
# Encoder-decoder models can only compile the pure decode steps where no
# encoder inputs are present. Use eager for the first pass.
num_encoder_reqs = len(scheduler_output.scheduled_encoder_inputs)
has_encoder_input = self.model_config.is_encoder_decoder and num_encoder_reqs > 0
# Run forward pass
with ProfileExecuteDuration().capture_async("forward"):
with set_ascend_forward_context(
attn_metadata,
self.vllm_config,
num_tokens=num_input_tokens,
num_tokens_across_dp=num_tokens_across_dp,
aclgraph_runtime_mode=aclgraph_runtime_mode,
batch_descriptor=batch_descriptor,
num_actual_tokens=scheduler_output.
total_num_scheduled_tokens,
model_instance=self.model):
self.maybe_setup_kv_connector(scheduler_output)
hidden_states = self._generate_process_reqs_hidden_states(
num_input_tokens, input_ids, positions,
intermediate_tensors, inputs_embeds, model_kwargs)
self.maybe_wait_for_kv_save()
finished_sending, finished_recving = self.get_finished_kv_transfer(
scheduler_output)
aux_hidden_states = None
if self.use_aux_hidden_state_outputs:
hidden_states, aux_hidden_states = hidden_states
kv_connector_output = KVConnectorOutput(
finished_sending=finished_sending,
finished_recving=finished_recving)
finished_sending = None
finished_recving = None
with ProfileExecuteDuration().capture_async("post process"):
with (
record_function_or_nullcontext("forward"),
set_ascend_forward_context(
attn_metadata,
self.vllm_config,
num_tokens=num_tokens_padded,
num_tokens_across_dp=num_tokens_across_dp,
aclgraph_runtime_mode=cudagraph_mode,
batch_descriptor=batch_desc,
num_actual_tokens=scheduler_output.total_num_scheduled_tokens,
model_instance=self.model,
skip_compiled=has_encoder_input,
),
self.maybe_get_kv_connector_output(scheduler_output) as kv_connector_output,
):
hidden_states = self._model_forward(
num_tokens_padded, input_ids, positions, intermediate_tensors, inputs_embeds, **model_kwargs
)
with record_function_or_nullcontext("post process"):
# -------------------------------------- Omni-new -------------------------------------------------
hidden_states, multimodal_outputs = self.extract_multimodal_outputs(hidden_states)
@@ -218,36 +310,36 @@ class NPUARModelRunner(OmniNPUModelRunner):
else:
logger.debug("[AR] execute_model: multimodal_outputs is None")
# -------------------------------------- Omni-new -------------------------------------------------
# Broadcast PP output for external_launcher (torchrun)
# to make sure we are synced across pp ranks
# TODO: Support overlapping mirco-batches
# https://github.com/vllm-project/vllm/issues/18019
broadcast_pp_output = \
self.parallel_config.distributed_executor_backend \
== "external_launcher" and len(get_pp_group().ranks) > 0
if not get_pp_group().is_last_rank:
# For mid-pipeline stages, return the hidden states.
if not broadcast_pp_output:
if self.pcp_size > 1:
# NOTE we must `slice` hidden_states because pcp_allgather_restore_idx
# ignores the padding from CUDA Graph.
hidden_states = self.pcp_manager.get_restore_hidden_states(hidden_states)
aux_hidden_states = None
if self.use_aux_hidden_state_outputs:
hidden_states, aux_hidden_states = hidden_states
if not self.broadcast_pp_output:
# Common case.
if not get_pp_group().is_last_rank:
# Return the intermediate tensors.
assert isinstance(hidden_states, IntermediateTensors)
hidden_states.kv_connector_output = kv_connector_output
self.kv_connector_output = kv_connector_output
if self.debugger is not None:
self.debugger.stop()
self.debugger.step()
return hidden_states
assert isinstance(hidden_states, IntermediateTensors)
get_pp_group().send_tensor_dict(
hidden_states.tensors, all_gather_group=get_tp_group())
logits = None
else:
if self.input_batch.pooling_params:
pool_output = self._pool(
hidden_states,
scheduler_output.total_num_scheduled_tokens,
num_scheduled_tokens_np, kv_connector_output)
if self.is_pooling_model:
# Return the pooling output.
output = self._pool(
hidden_states, num_scheduled_tokens, num_scheduled_tokens_np, kv_connector_output
)
output.kv_connector_output = kv_connector_output
if self.debugger is not None:
self.debugger.stop()
self.debugger.step()
return pool_output
return output
sample_hidden_states = hidden_states[logits_indices]
# -------------------------------------- Omni-new -------------------------------------------------
# Try with sampling_metadata first; fall back to without for models that don't support it
@@ -258,37 +350,56 @@ class NPUARModelRunner(OmniNPUModelRunner):
except TypeError:
logits = self.model.compute_logits(sample_hidden_states)
# -------------------------------------- Omni-new -------------------------------------------------
if broadcast_pp_output:
model_output_broadcast_data = {
"logits": logits.contiguous(),
} if logits is not None else {}
model_output_broadcast_data = get_pp_group(
).broadcast_tensor_dict(model_output_broadcast_data,
src=len(get_pp_group().ranks) - 1)
assert model_output_broadcast_data is not None
logits = model_output_broadcast_data["logits"]
else:
# Rare case.
assert not self.is_pooling_model
if not get_pp_group().is_last_rank:
sample_hidden_states = hidden_states[logits_indices]
get_pp_group().send_tensor_dict(hidden_states.tensors, all_gather_group=get_tp_group())
logits = None
else:
sample_hidden_states = hidden_states[logits_indices]
# -------------------------------------- Omni-new -------------------------------------------------
# Try with sampling_metadata first; fall back to without for models that don't support it
try:
logits = self.model.compute_logits(
sample_hidden_states, sampling_metadata=self.input_batch.sampling_metadata
)
except TypeError:
logits = self.model.compute_logits(sample_hidden_states)
# -------------------------------------- Omni-new -------------------------------------------------
model_output_broadcast_data: dict[str, Any] = {}
if logits is not None:
model_output_broadcast_data["logits"] = logits.contiguous()
broadcasted = get_pp_group().broadcast_tensor_dict(
model_output_broadcast_data, src=len(get_pp_group().ranks) - 1
)
assert broadcasted is not None
logits = broadcasted["logits"]
# Apply structured output bitmasks if present
self.execute_model_state = ExecuteModelState(
scheduler_output,
logits,
spec_decode_metadata,
spec_decode_common_attn_metadata,
hidden_states,
sample_hidden_states,
aux_hidden_states,
kv_connector_output,
attn_metadata,
positions,
ec_connector_output,
multimodal_outputs, # Omni-new
cudagraph_stats,
multimodal_outputs,
)
self.kv_connector_output = kv_connector_output
return None
@torch.inference_mode()
def sample_tokens(
self,
grammar_output: GrammarOutput | None = None,
self, grammar_output: GrammarOutput | None
) -> OmniModelRunnerOutput | AsyncModelRunnerOutput | IntermediateTensors:
kv_connector_output = self.kv_connector_output
self.kv_connector_output = None
@@ -298,6 +409,7 @@ class NPUARModelRunner(OmniNPUModelRunner):
self.kv_extracted_req_ids = None
# -------------------------------------- Omni-new -------------------------------------------------
if self.execute_model_state is None:
# Nothing to do (PP non-final rank case), output isn't used.
if not kv_connector_output:
@@ -316,13 +428,14 @@ class NPUARModelRunner(OmniNPUModelRunner):
scheduler_output,
logits,
spec_decode_metadata,
spec_decode_common_attn_metadata,
hidden_states,
sample_hidden_states,
aux_hidden_states,
kv_connector_output,
attn_metadata,
positions,
ec_connector_output,
cudagraph_stats,
multimodal_outputs,
) = self.execute_model_state
# Clear ephemeral state.
@@ -334,26 +447,26 @@ class NPUARModelRunner(OmniNPUModelRunner):
# the apply_grammar_bitmask uses torch.compile to optimize this,ascend does not support it now
logits_dtype = logits.dtype
logits = logits.to("cpu").float()
apply_grammar_bitmask(scheduler_output, grammar_output,
self.input_batch, logits)
apply_grammar_bitmask(scheduler_output, grammar_output, self.input_batch, logits)
logits = logits.to(self.device).to(logits_dtype)
with ProfileExecuteDuration().capture_async("Sample"):
with record_function_or_nullcontext("sample_token"):
sampler_output = self._sample(logits, spec_decode_metadata)
def propose_draft_token_ids(sampled_token_ids):
assert self.spec_decode_common_attn_metadata is not None
assert spec_decode_common_attn_metadata is not None
self._draft_token_ids = self.propose_draft_token_ids(
sampled_token_ids,
self.input_batch.sampling_metadata,
scheduler_output,
spec_decode_metadata,
spec_decode_common_attn_metadata,
positions,
scheduler_output.total_num_scheduled_tokens,
hidden_states,
attn_metadata,
aux_hidden_states,
sample_hidden_states
sample_hidden_states,
)
self._copy_draft_token_ids_to_cpu(scheduler_output)
@@ -373,11 +486,13 @@ class NPUARModelRunner(OmniNPUModelRunner):
spec_decode_metadata,
)
with ProfileExecuteDuration().capture_async("Draft"):
with record_function_or_nullcontext("draft_token"):
if self.speculative_config:
use_padded_batch_for_eagle = self.speculative_config and \
self.speculative_config.use_eagle() and \
not self.speculative_config.disable_padded_drafter_batch
use_padded_batch_for_eagle = (
self.speculative_config
and self.speculative_config.use_eagle()
and not self.speculative_config.disable_padded_drafter_batch
)
if use_padded_batch_for_eagle:
# EAGLE speculative decoding can use the GPU sampled tokens
# as inputs, and does not need to wait for bookkeeping to finish.
@@ -389,7 +504,6 @@ class NPUARModelRunner(OmniNPUModelRunner):
if has_kv_transfer_group():
get_kv_transfer_group().clear_connector_metadata()
# -------------------------------------- Omni-new -------------------------------------------------
hidden_states_cpu = hidden_states.detach().to("cpu").contiguous()
num_scheduled_tokens_np = getattr(self, "_omni_num_scheduled_tokens_np", None)
@@ -448,28 +562,15 @@ class NPUARModelRunner(OmniNPUModelRunner):
model_runner_output.kv_extracted_req_ids = kv_extracted_req_ids
# -------------------------------------- Omni-new -------------------------------------------------
durations = ProfileExecuteDuration().pop_captured_sync()
if durations:
dr_str = [
f"[{tag}]:{duration:.2f}ms"
for tag, duration in durations.items()
]
captured_name = "Decode" if self.attn_state == AscendAttentionState.DecodeOnly else "Prefill"
logger.info("Profile execute duration [%s]:%s", captured_name,
" ".join(dr_str))
if self.dynamic_eplb:
self.eplb_updator.forward_end()
if not self.use_async_scheduling:
if self.debugger is not None:
assert self.debugger is not None
self.debugger.stop()
self.debugger.step()
return model_runner_output
if self.debugger is not None:
assert self.debugger is not None
self.debugger.stop()
self.debugger.step()
if not self.use_async_scheduling:
return model_runner_output
return AsyncGPUModelRunnerOutput(
model_runner_output=model_runner_output,
sampled_token_ids=sampler_output.sampled_token_ids,
@@ -479,33 +580,6 @@ class NPUARModelRunner(OmniNPUModelRunner):
vocab_size=self.input_batch.vocab_size,
)
def _generate_process_reqs_hidden_states(self, num_input_tokens,
input_ids, positions,
intermediate_tensors,
inputs_embeds, model_kwargs):
assert self.model is not None
hidden_states = self._model_forward(input_ids=input_ids,
positions=positions,
intermediate_tensors=intermediate_tensors,
inputs_embeds=inputs_embeds,
**model_kwargs)
forward_context = get_forward_context()
if forward_context.cudagraph_runtime_mode == CUDAGraphMode.FULL \
and not self.use_sparse:
if self.vllm_config.model_config.use_mla:
if self.pcp_size * self.dcp_size > 1:
update_full_graph_params(self.attn_backend, self.update_stream, forward_context,
num_input_tokens, self.vllm_config,
self.vllm_config.speculative_config)
if get_forward_context().sp_enabled and not isinstance(
hidden_states, IntermediateTensors):
hidden_states = self._all_gather_hidden_states_and_aux(
hidden_states)
return hidden_states if self.pcp_size == 1 else self.pcp_manager.get_restore_hidden_states(
hidden_states)
def _resolve_global_request_id(self, req_id: str) -> str:
"""Resolve global request ID from request state."""
req_state = self.requests.get(req_id)
@@ -4,8 +4,7 @@
from __future__ import annotations
import gc
import math
from copy import copy
from copy import copy, deepcopy
import numpy as np
import torch
@@ -19,12 +18,16 @@ from vllm.sequence import IntermediateTensors
from vllm.utils.math_utils import cdiv
from vllm.v1.core.sched.output import GrammarOutput, SchedulerOutput
from vllm.v1.outputs import EMPTY_MODEL_RUNNER_OUTPUT, AsyncModelRunnerOutput, make_empty_encoder_model_runner_output
from vllm.v1.worker.gpu_model_runner import AsyncGPUModelRunnerOutput
from vllm.v1.worker.kv_connector_model_runner_mixin import KVConnectorOutput
from vllm_ascend.ascend_forward_context import MoECommType, get_mc2_tokens_capacity, set_ascend_forward_context
from vllm.v1.utils import record_function_or_nullcontext
from vllm.v1.worker.gpu_model_runner import AsyncGPUModelRunnerOutput, PerLayerAttnMetadata
from vllm.v1.worker.ubatch_utils import maybe_create_ubatch_slices
from vllm.v1.worker.utils import sanity_check_mm_encoder_outputs
from vllm_ascend.ascend_forward_context import set_ascend_forward_context
from vllm_ascend.attention.attention_v1 import AscendAttentionState
from vllm_ascend.attention.utils import using_paged_attention
from vllm_ascend.ops.rotary_embedding import update_cos_sin
from vllm_ascend.platform import NPUPlatform
from vllm_ascend.utils import ProfileExecuteDuration, enable_sp, lmhead_tp_enable
from vllm_ascend.utils import enable_sp, lmhead_tp_enable
from vllm_ascend.worker.model_runner_v1 import SEQ_LEN_WITH_MAX_PA_WORKSPACE
from vllm_omni.outputs import OmniModelRunnerOutput
from vllm_omni.platforms.npu.worker.npu_ar_model_runner import ExecuteModelState
@@ -35,15 +38,31 @@ class NPUGenerationModelRunner(OmniNPUModelRunner):
"""Generation model runner for vLLM-omni on NPU (non-autoregressive)."""
def _update_request_states(self, scheduler_output: SchedulerOutput):
# remove requests
for req_id in scheduler_output.finished_req_ids:
self.input_batch.remove_request(req_id)
scheduled_req_ids = scheduler_output.num_scheduled_tokens.keys()
cached_req_ids = self.input_batch.req_id_to_index.keys()
resumed_req_ids = scheduler_output.scheduled_cached_reqs.resumed_req_ids
unscheduled_req_ids = cached_req_ids - (scheduled_req_ids - resumed_req_ids)
for req_id in unscheduled_req_ids:
self.input_batch.remove_request(req_id)
cached_reqs = scheduler_output.scheduled_cached_reqs
for _, req_id in enumerate(cached_reqs.req_ids):
req_states = []
for req_id in cached_reqs.req_ids:
req_state = self.requests.get(req_id)
assert req_state is not None
req_state.prompt_token_ids = cached_reqs.prompt_token_ids.get(req_id)
self.input_batch.remove_request(req_id)
req_states.append(req_state)
# Remove the request from the current input batch only if it is still present.
if req_id in self.input_batch.req_id_to_index:
self.input_batch.remove_request(req_id)
for req_state in req_states:
# update the request state in self.input_batch
self.input_batch.add_request(req_state)
self._init_mrope_positions(req_state)
# Only relevant for models using M-RoPE (e.g, Qwen2-VL)
if self.uses_mrope:
self._init_mrope_positions(req_state)
@torch.inference_mode()
def execute_model(
@@ -53,51 +72,169 @@ class NPUGenerationModelRunner(OmniNPUModelRunner):
) -> OmniModelRunnerOutput | IntermediateTensors:
if self.execute_model_state is not None:
raise RuntimeError("State error: sample_tokens() must be called after execute_model() returns None.")
with ProfileExecuteDuration().capture_async("prepare input"):
# self._draft_token_ids is None when `input_fits_in_drafter=False`
# and there is no draft tokens scheduled. so it need to update the
# spec_decoding info in scheduler_output with async_scheduling.
# use deepcopy to avoid the modification has influence on the
# scheduler_output in engine core process.
# TODO(Ronald1995): deepcopy is expensive when there is a large
# number of requests, optimize it later.
if (
self.use_async_scheduling and self.num_spec_tokens and self._draft_token_ids is None # type: ignore[has-type]
):
scheduler_output = deepcopy(scheduler_output)
num_scheduled_tokens = scheduler_output.total_num_scheduled_tokens
with record_function_or_nullcontext("prepare input"):
# -------------------------------------- Omni-new -------------------------------------------------
if self.model_config.async_chunk:
self._update_request_states(scheduler_output)
# -------------------------------------- Omni-new -------------------------------------------------
self._update_states(scheduler_output)
if has_ec_transfer() and get_ec_transfer().is_producer:
with self.maybe_get_ec_connector_output(
with self.synchronize_input_prep():
# Update persistent batch states.
self._update_states(scheduler_output)
if has_ec_transfer() and get_ec_transfer().is_producer:
with self.maybe_get_ec_connector_output(
scheduler_output,
encoder_cache=self.encoder_cache,
) as ec_connector_output:
self._execute_mm_encoder(scheduler_output)
return make_empty_encoder_model_runner_output(scheduler_output)
if not num_scheduled_tokens:
if (
self.parallel_config.distributed_executor_backend == "external_launcher"
and self.parallel_config.data_parallel_size > 1
):
# this is a corner case when both external launcher
# and DP are enabled, num_scheduled_tokens could be
# 0, and has_unfinished_requests in the outer loop
# returns True. before returning early here we call
# dummy run to ensure coordinate_batch_across_dp
# is called into to avoid out of sync issues.
self._dummy_run(1)
if not has_kv_transfer_group():
# Return empty ModelRunnerOutput if no work to do.
return EMPTY_MODEL_RUNNER_OUTPUT
return self.kv_connector_no_forward(scheduler_output, self.vllm_config)
if self.cache_config.kv_sharing_fast_prefill:
assert not self.num_prompt_logprobs, (
"--kv-sharing-fast-prefill produces incorrect "
"logprobs for prompt tokens, tokens, please disable "
"it when the requests need prompt logprobs"
)
num_reqs = self.input_batch.num_reqs
req_ids = self.input_batch.req_ids
tokens = [scheduler_output.num_scheduled_tokens[i] for i in req_ids]
num_scheduled_tokens_np = np.array(tokens, dtype=np.int32)
max_num_scheduled_tokens = int(num_scheduled_tokens_np.max())
(
logits_indices,
spec_decode_metadata,
) = self._prepare_inputs(
scheduler_output,
encoder_cache=self.encoder_cache,
) as ec_connector_output:
self._execute_mm_encoder(scheduler_output)
return make_empty_encoder_model_runner_output(scheduler_output)
num_scheduled_tokens_np,
)
if not scheduler_output.total_num_scheduled_tokens:
if not has_kv_transfer_group():
logger.debug("skip this step for we receive the data from remote disaggregate prefill node")
# Return empty ModelRunnerOutput if there's no work to do.
return EMPTY_MODEL_RUNNER_OUTPUT
return self.kv_connector_no_forward(scheduler_output, self.vllm_config)
num_tokens_unpadded = scheduler_output.total_num_scheduled_tokens
if self.pcp_size > 1:
num_tokens_unpadded = self.pcp_manager.total_num_sampled_tokens_pcp
cascade_attn_prefix_lens = None
# Disable cascade attention when using microbatching (DBO)
if self.cascade_attn_enabled and not self.parallel_config.enable_dbo:
# Pre-compute cascade attention prefix lengths
cascade_attn_prefix_lens = self._compute_cascade_attn_prefix_lens(
num_scheduled_tokens_np,
self.input_batch.num_computed_tokens_cpu[:num_reqs],
scheduler_output.num_common_prefix_blocks,
)
if self.dynamic_eplb:
self.eplb_updator.forward_before()
(
cudagraph_mode,
batch_desc,
should_ubatch,
num_tokens_across_dp,
cudagraph_stats,
) = self._determine_batch_execution_and_padding(
num_tokens=num_tokens_unpadded,
num_reqs=num_reqs,
num_scheduled_tokens_np=num_scheduled_tokens_np,
max_num_scheduled_tokens=max_num_scheduled_tokens,
use_cascade_attn=cascade_attn_prefix_lens is not None,
num_encoder_reqs=len(scheduler_output.scheduled_encoder_inputs),
)
logger.debug(
"Running batch with cudagraph_mode: %s, batch_descriptor: %s, "
"should_ubatch: %s, num_tokens_across_dp: %s",
cudagraph_mode,
batch_desc,
should_ubatch,
num_tokens_across_dp,
)
num_tokens_padded = batch_desc.num_tokens
num_reqs_padded = batch_desc.num_reqs if batch_desc.num_reqs is not None else num_reqs
ubatch_slices, ubatch_slices_padded = maybe_create_ubatch_slices(
should_ubatch,
num_scheduled_tokens_np,
num_tokens_padded,
num_reqs_padded,
self.parallel_config.num_ubatches,
)
pad_attn = cudagraph_mode == CUDAGraphMode.FULL
use_spec_decode = len(scheduler_output.scheduled_spec_decode_tokens) > 0
ubatch_slices_attn = ubatch_slices_padded if pad_attn else ubatch_slices
if (
cudagraph_mode == CUDAGraphMode.FULL
or (enable_sp() and not self.model_config.use_mla)
and self.pcp_size == 1 # TODO(lxs): fix this
):
# Currently, Graph Mode and SP will both pad num_tokens,
# Another possible condition is num_tokens_padded != num_tokens_unpadded
# but this scope is way too big and the consequences are unpredictable
num_reqs_padded = self._pad_query_start_loc_for_fia(num_tokens_padded, num_reqs_padded, num_reqs)
(attn_metadata, spec_decode_common_attn_metadata) = self._build_attention_metadata(
num_tokens=num_tokens_unpadded,
num_tokens_padded=num_tokens_padded,
num_reqs=num_reqs,
num_reqs_padded=num_reqs_padded,
max_query_len=max_num_scheduled_tokens,
ubatch_slices=ubatch_slices_attn,
logits_indices=logits_indices,
use_spec_decode=use_spec_decode,
num_scheduled_tokens=scheduler_output.num_scheduled_tokens,
num_scheduled_tokens_np=num_scheduled_tokens_np,
cascade_attn_prefix_lens=cascade_attn_prefix_lens,
)
(
attn_metadata,
num_scheduled_tokens_np,
num_input_tokens,
num_tokens_across_dp,
logits_indices,
spec_decode_metadata,
max_query_len,
) = self._prepare_inputs(scheduler_output)
input_ids,
inputs_embeds,
positions,
intermediate_tensors,
model_kwargs,
ec_connector_output,
) = self._preprocess(scheduler_output, num_tokens_padded, intermediate_tensors)
(input_ids, inputs_embeds, positions, intermediate_tensors, model_kwargs, ec_connector_output) = (
self._preprocess(scheduler_output, num_input_tokens, intermediate_tensors)
)
# [Omni] Pass token counts per request for code2wav output slicing
model_kwargs["seq_token_counts"] = tokens
# update global cos, sin
update_cos_sin(positions)
if self.dynamic_eplb:
self.eplb_updator.take_update_info_from_eplb_process()
# Set cudagraph mode to none if calc_kv_scales is true.
# KV scales calculation involves dynamic operations that are incompatible
# with CUDA graph capture.
if self.calculate_kv_scales: # type: ignore[has-type]
cudagraph_mode = CUDAGraphMode.NONE
# Mark KV scales as calculated after the first forward pass
self.calculate_kv_scales = False # type: ignore[has-type]
# prevent debugger is None
if self.debugger is not None:
dbg_cfg = getattr(self.debugger, "config", None)
@@ -106,15 +243,6 @@ class NPUGenerationModelRunner(OmniNPUModelRunner):
self.debugger.start(model=self.model)
else:
self.debugger.start()
uniform_decode = (max_query_len == self.uniform_decode_query_len) and (
scheduler_output.total_num_scheduled_tokens == self.input_batch.num_reqs * max_query_len
)
has_lora = len(self.input_batch.lora_id_to_lora_request) > 0
aclgraph_runtime_mode, batch_descriptor = self.cudagraph_dispatcher.dispatch(
num_tokens=num_input_tokens, uniform_decode=uniform_decode, has_lora=has_lora
)
if self.ascend_config.enable_async_exponential:
self.sampler.do_async_exponential(
b_s=logits_indices.shape[0],
@@ -122,63 +250,60 @@ class NPUGenerationModelRunner(OmniNPUModelRunner):
generators=self.input_batch.sampling_metadata.generators,
)
# Encoder-decoder models can only compile the pure decode steps where no
# encoder inputs are present. Use eager for the first pass.
num_encoder_reqs = len(scheduler_output.scheduled_encoder_inputs)
has_encoder_input = self.model_config.is_encoder_decoder and num_encoder_reqs > 0
# Run forward pass
with ProfileExecuteDuration().capture_async("forward"):
with set_ascend_forward_context(
with (
record_function_or_nullcontext("forward"),
set_ascend_forward_context(
attn_metadata,
self.vllm_config,
num_tokens=num_input_tokens,
num_tokens=num_tokens_padded,
num_tokens_across_dp=num_tokens_across_dp,
aclgraph_runtime_mode=aclgraph_runtime_mode,
batch_descriptor=batch_descriptor,
aclgraph_runtime_mode=cudagraph_mode,
batch_descriptor=batch_desc,
num_actual_tokens=scheduler_output.total_num_scheduled_tokens,
model_instance=self.model,
):
self.maybe_setup_kv_connector(scheduler_output)
# -------------------------------------- Omni-new -------------------------------------------------
outputs = self._run_generation_model(
input_ids=input_ids,
positions=positions,
intermediate_tensors=intermediate_tensors,
inputs_embeds=inputs_embeds,
model_kwargs=model_kwargs,
logits_indices=logits_indices,
)
# -------------------------------------- Omni-new -------------------------------------------------
self.maybe_wait_for_kv_save()
finished_sending, finished_recving = self.get_finished_kv_transfer(scheduler_output)
aux_hidden_states = None
if self.use_aux_hidden_state_outputs:
outputs, aux_hidden_states = outputs
kv_connector_output = KVConnectorOutput(finished_sending=finished_sending, finished_recving=finished_recving)
finished_sending = None
finished_recving = None
_, multimodal_outputs = self.extract_multimodal_outputs(outputs)
# Apply structured output bitmasks if present
self.execute_model_state = ExecuteModelState(
scheduler_output,
None,
spec_decode_metadata,
outputs,
None,
aux_hidden_states,
kv_connector_output,
attn_metadata,
positions,
ec_connector_output,
multimodal_outputs,
)
self.kv_connector_output = kv_connector_output
skip_compiled=has_encoder_input,
),
self.maybe_get_kv_connector_output(scheduler_output) as kv_connector_output,
):
# -------------------------------------- Omni-new -------------------------------------------------
outputs = self._run_generation_model(
num_tokens_padded=num_tokens_padded,
input_ids=input_ids,
positions=positions,
intermediate_tensors=intermediate_tensors,
inputs_embeds=inputs_embeds,
model_kwargs=model_kwargs,
logits_indices=logits_indices,
)
_, multimodal_outputs = self.extract_multimodal_outputs(outputs)
# -------------------------------------- Omni-new -------------------------------------------------
# Apply structured output bitmasks if present
self.execute_model_state = ExecuteModelState(
scheduler_output,
None,
spec_decode_metadata,
spec_decode_common_attn_metadata,
None,
None,
None,
attn_metadata,
positions,
ec_connector_output,
cudagraph_stats,
multimodal_outputs, # Omni-new: pass multimodal_outputs to ExecuteModelState
)
self.kv_connector_output = kv_connector_output
return None
@torch.inference_mode()
def sample_tokens(
self,
grammar_output: GrammarOutput | None = None,
self, grammar_output: GrammarOutput | None
) -> OmniModelRunnerOutput | AsyncModelRunnerOutput | IntermediateTensors:
kv_connector_output = self.kv_connector_output
self.kv_connector_output = None
@@ -201,13 +326,14 @@ class NPUGenerationModelRunner(OmniNPUModelRunner):
scheduler_output,
logits,
spec_decode_metadata,
spec_decode_common_attn_metadata,
hidden_states,
sample_hidden_states,
aux_hidden_states,
kv_connector_output,
attn_metadata,
positions,
ec_connector_output,
cudagraph_stats,
multimodal_outputs,
) = self.execute_model_state
# Clear ephemeral state.
@@ -231,11 +357,22 @@ class NPUGenerationModelRunner(OmniNPUModelRunner):
{"model_outputs": out.detach().to("cpu").contiguous() if out is not None else None}
)
elif isinstance(multimodal_outputs, dict):
mm_payload = {}
for key, out in multimodal_outputs.items():
if out is not None and isinstance(out, torch.Tensor):
mm_payload[key] = out.detach().to("cpu").contiguous()
pooler_output.append(mm_payload)
num_reqs = self.input_batch.num_reqs
for i in range(num_reqs):
mm_payload = {}
for key, out in multimodal_outputs.items():
if isinstance(out, list):
if len(out) != num_reqs:
raise ValueError(
f"Multimodal output list for key '{key}' has length {len(out)} "
f"but expected {num_reqs} (one entry per request)."
)
mm_payload[key] = out[i].detach().to("cpu").contiguous()
elif isinstance(out, torch.Tensor):
mm_payload[key] = out.detach().to("cpu").contiguous()
else:
logger.warning(f"Unsupported multimodal output type for key '{key}': {type(out)}")
pooler_output.append(mm_payload)
else:
raise RuntimeError("Unsupported diffusion output type")
# [Omni] Copy req_id mappings to avoid async scheduling mutation.
@@ -253,20 +390,25 @@ class NPUGenerationModelRunner(OmniNPUModelRunner):
ec_connector_output=ec_connector_output if self.supports_mm_inputs else None,
)
# -------------------------------------- Omni-new -------------------------------------------------
if self.debugger is not None:
self.debugger.stop()
self.debugger.step()
if not self.use_async_scheduling:
return output
return AsyncGPUModelRunnerOutput(
model_runner_output=output,
sampled_token_ids=torch.tensor([], device=self.device),
logprobs_tensors=None,
invalid_req_indices=[],
async_output_copy_stream=self.async_output_copy_stream,
vocab_size=self.input_batch.vocab_size,
logprobs_tensors=None,
)
def _run_generation_model(
self,
*,
num_tokens_padded: int,
input_ids: torch.Tensor | None,
positions: torch.Tensor | None,
intermediate_tensors: IntermediateTensors | None,
@@ -285,6 +427,7 @@ class NPUGenerationModelRunner(OmniNPUModelRunner):
"""
# Keep inputs identical to AR runner
kwargs = dict(
num_tokens_padded=num_tokens_padded,
input_ids=input_ids,
positions=positions,
intermediate_tensors=intermediate_tensors,
@@ -317,38 +460,16 @@ class NPUGenerationModelRunner(OmniNPUModelRunner):
force_attention: bool = False,
uniform_decode: bool = False,
is_profile: bool = False,
create_mixed_batch: bool = False,
allow_microbatching: bool = True,
skip_eplb: bool = False,
remove_lora: bool = True,
activate_lora: bool = False,
is_graph_capturing: bool = False,
) -> torch.Tensor:
num_active_loras: int = 0,
) -> tuple[torch.Tensor, torch.Tensor]:
# only support eager mode and piecewise graph now
assert cudagraph_runtime_mode is None or cudagraph_runtime_mode in {
CUDAGraphMode.NONE,
CUDAGraphMode.PIECEWISE,
CUDAGraphMode.FULL,
}
# In multi-DP scenarios, there may be situations where all DP groups are executing dummy runs.
# If sequence parallelism is enabled, it is essential to ensure that num_tokens is divisible by tp_size.
if self.use_aclgraph and enable_sp(self.vllm_config):
tp_size = self.vllm_config.parallel_config.tensor_parallel_size
num_tokens = math.ceil(num_tokens / tp_size) * tp_size
# Force dummy run on prefill stage when this node is deemed as kv producer.
if self.is_kv_producer and not self.is_kv_consumer:
with_prefill = True
has_lora = True if self.lora_config and self.compilation_config.cudagraph_specialize_lora else False
_ag_mode, batch_descriptor = self.cudagraph_dispatcher.dispatch(
num_tokens=num_tokens, uniform_decode=uniform_decode, has_lora=has_lora
)
# Padding for DP
(num_tokens, num_tokens_across_dp, with_prefill) = self._sync_metadata_across_dp(
batch_descriptor.num_tokens, with_prefill
)
assert cudagraph_runtime_mode is None or cudagraph_runtime_mode.valid_runtime_modes()
# If cudagraph_mode.decode_mode() == FULL and
# cudagraph_mode.separate_routine(). This means that we are using
# different graphs and/or modes for mixed prefill-decode batches vs.
@@ -363,78 +484,126 @@ class NPUGenerationModelRunner(OmniNPUModelRunner):
# routine of FA2 for pure decode, i.e., Flashdecode + an optimization
# for GQA/MQA.
max_query_len = self.uniform_decode_query_len if uniform_decode else num_tokens
# Set num_scheduled_tokens based on num_tokens and max_num_seqs
# for dummy run with LoRA so that the num_reqs collectively
# has num_tokens in total.
assert num_tokens <= self.scheduler_config.max_num_batched_tokens
max_num_reqs = self.max_num_reqs
if uniform_decode:
num_reqs = cdiv(num_tokens, max_query_len)
max_num_reqs = self.scheduler_config.max_num_seqs
if create_mixed_batch:
raise NotImplementedError("create_mixed_batch is used for warmup deepgemm, vllm-ascend does not need it")
elif uniform_decode:
assert not create_mixed_batch
num_reqs = min(max_num_reqs, cdiv(num_tokens, max_query_len))
num_scheduled_tokens_list = [max_query_len] * num_reqs
if num_tokens % max_query_len != 0:
num_scheduled_tokens_list[-1] = num_tokens % max_query_len
else:
if with_prefill:
num_reqs = num_tokens
else:
num_reqs = (num_tokens + self.decode_token_per_req - 1) // self.decode_token_per_req
num_reqs = min(num_reqs, max_num_reqs)
num_reqs = min(num_tokens, max_num_reqs)
min_tokens_per_req = num_tokens // num_reqs
num_scheduled_tokens_list = [min_tokens_per_req] * num_reqs
num_scheduled_tokens_list[-1] += num_tokens % num_reqs
assert sum(num_scheduled_tokens_list) == num_tokens
assert len(num_scheduled_tokens_list) == num_reqs
num_scheduled_tokens = np.array(num_scheduled_tokens_list, dtype=np.int32)
self.query_lens = torch.from_numpy(num_scheduled_tokens)
num_tokens_unpadded = int(num_scheduled_tokens.sum())
num_sampled_tokens = np.ones(num_reqs, dtype=np.int32)
if not is_profile and self.dynamic_eplb:
self.eplb_updator.forward_before()
if num_tokens != batch_descriptor.num_tokens:
_ag_mode, batch_descriptor = self.cudagraph_dispatcher.dispatch(
num_tokens=num_tokens, uniform_decode=uniform_decode, has_lora=has_lora
_cudagraph_mode, batch_desc, _, num_tokens_across_dp, _ = self._determine_batch_execution_and_padding(
num_tokens=num_tokens_unpadded,
num_reqs=num_reqs,
num_scheduled_tokens_np=num_scheduled_tokens,
max_num_scheduled_tokens=max_query_len,
use_cascade_attn=False,
allow_microbatching=allow_microbatching,
force_eager=is_profile or (cudagraph_runtime_mode == CUDAGraphMode.NONE),
# `force_uniform_decode` is used for cudagraph capture; because for
# capturing mixed prefill-decode batches, we sometimes use
# num_tokens == num_reqs which looks like a uniform decode batch to the
# dispatcher; but we actually want to capture a piecewise cudagraph
force_uniform_decode=uniform_decode,
# `force_has_lora` is used for cudagraph capture; because LoRA is
# activated later in the context manager, but we need to know the
# LoRA state when determining the batch descriptor for capture
force_has_lora=activate_lora,
)
if self.use_cp:
self.pcp_manager.init_batch_info(
num_scheduled_tokens,
num_reqs,
)
num_tokens_padded = batch_descriptor.num_tokens
num_reqs_padded = batch_descriptor.num_reqs if batch_descriptor.num_reqs is not None else num_reqs
if self.speculative_config:
self.pcp_manager.query_lens_pcp_full.cpu[:num_reqs] = torch.from_numpy(num_scheduled_tokens)
self.pcp_manager.query_lens_pcp_full.copy_to_gpu()
if cudagraph_runtime_mode is None:
cudagraph_runtime_mode = _cudagraph_mode
else:
assert cudagraph_runtime_mode == _cudagraph_mode, (
f"Cudagraph runtime mode mismatch in dummy_run. "
f"Expected {_cudagraph_mode}, but got {cudagraph_runtime_mode}."
)
num_tokens_padded = batch_desc.num_tokens
num_reqs_padded = batch_desc.num_reqs if batch_desc.num_reqs is not None else num_reqs
if num_tokens_across_dp is not None and num_tokens_padded != num_tokens:
# pad is needed if the pad of `num_tokens` is triggered inside CudagraphDispatcher
num_tokens_across_dp[:] = num_tokens_padded
num_scheduled_tokens = num_scheduled_tokens.repeat(num_reqs_padded)
# filter out the valid batch descriptor
if cudagraph_runtime_mode is not None:
# we allow forcing NONE when the dispatcher disagrees to support
# warm ups for aclgraph capture
if cudagraph_runtime_mode != CUDAGraphMode.NONE and cudagraph_runtime_mode != _ag_mode:
raise ValueError(
f"Aclgraph runtime mode mismatch at dummy_run. "
f"Expected {_ag_mode}, but got {cudagraph_runtime_mode}."
# vllm-ascend does not support ubatch now
ubatch_slices, ubatch_slices_padded = None, None
attn_metadata: PerLayerAttnMetadata | None = None
# If force_attention is True, we always capture attention. Otherwise,
# it only happens for cudagraph_runtime_mode=FULL.
if force_attention or cudagraph_runtime_mode == CUDAGraphMode.FULL:
if create_mixed_batch:
raise NotImplementedError(
"create_mixed_batch is used for warmup deepgemm, vllm-ascend does not need it"
)
else:
cudagraph_runtime_mode = _ag_mode
self.attn_state = AscendAttentionState.DecodeOnly
if self.speculative_config and self.speculative_config.method == "mtp":
# `AscendAttentionState.SpecDecoding` is only designed for mla
if self.vllm_config.model_config.use_mla:
self.attn_state = AscendAttentionState.SpecDecoding
else:
self.attn_state = AscendAttentionState.ChunkedPrefill
# The reason why we use a fixed seq_len rather than max_query_len is that
# _npu_paged_attention_get_workspace only returns max workspace with specific
# seq_lens. We use this seq_len only when capturing graph, and still use max_query_len
# in inference. This will be removed once npu_fused_infer_attention_score
# outperforms _npu_paged_attention on all cases.
seq_lens = (
SEQ_LEN_WITH_MAX_PA_WORKSPACE
if is_graph_capturing and using_paged_attention(num_tokens, self.vllm_config)
else max_query_len
) # type: ignore[assignment]
self.seq_lens.np[:num_reqs_padded] = seq_lens
self.seq_lens.np[num_reqs_padded:] = 0
self.seq_lens.copy_to_gpu()
# TODO(Mengqing): Set create_mixed_batch to False since it's only used in FI warmup
# and not supported in ASCEND now. We could remove it in the future.
attn_metadata = self._build_dummy_attn_metadata(
False,
num_reqs=num_reqs_padded,
num_tokens=num_tokens_padded,
max_query_len=max_query_len,
aclgraph_runtime_mode=cudagraph_runtime_mode,
force_attention=force_attention,
is_graph_capturing=is_graph_capturing,
num_scheduled_tokens=num_scheduled_tokens,
)
cum_num_tokens, _ = self._get_cumsum_and_arange(num_scheduled_tokens)
self.query_start_loc.np[1 : num_reqs_padded + 1] = cum_num_tokens
self.query_start_loc.copy_to_gpu()
with self.maybe_dummy_run_with_lora(self.lora_config, num_scheduled_tokens, num_sampled_tokens):
num_reqs_padded = self._pad_query_start_loc_for_fia(num_tokens_padded, num_reqs_padded, num_reqs)
pad_attn = cudagraph_runtime_mode == CUDAGraphMode.FULL
attn_metadata, _ = self._build_attention_metadata(
num_tokens=num_tokens_unpadded,
num_tokens_padded=num_tokens_padded,
num_reqs=num_reqs_padded,
max_query_len=max_query_len,
ubatch_slices=ubatch_slices_padded if pad_attn else ubatch_slices,
for_cudagraph_capture=is_graph_capturing,
num_scheduled_tokens_np=num_scheduled_tokens,
)
with self.maybe_dummy_run_with_lora(
self.lora_config,
num_scheduled_tokens,
num_sampled_tokens,
):
# Make sure padding doesn't exceed max_num_tokens
assert num_tokens_padded <= self.max_num_tokens
if self.is_multimodal_model and not self.model_config.is_encoder_decoder:
input_ids = None
inputs_embeds = self.inputs_embeds.gpu[:num_tokens_padded]
elif self.enable_prompt_embeds:
if self.is_multimodal_model and not self.model_config.is_encoder_decoder or self.enable_prompt_embeds:
input_ids = None
inputs_embeds = self.inputs_embeds.gpu[:num_tokens_padded]
else:
@@ -454,20 +623,22 @@ class NPUGenerationModelRunner(OmniNPUModelRunner):
if get_pp_group().is_first_rank:
intermediate_tensors = None
else:
# When PP and flashcomm1 are enabled,
# during dummy_run the estimated space should divide num_tokens by tp_size;
# otherwise, on non-first PP ranks it would effectively perform an extra all-gather,
# leading to incorrect memory estimation and potentially causing OOM.
actual_tokens = num_tokens
# When PP and flashcomm1 are enabled, during dummy_run the estimated space should divide num_tokens by
# tp_size; otherwise, on non-first PP ranks it would effectively perform an extra all-gather, leading
# to incorrect memory estimation and potentially causing OOM.
intermediate_tokens = num_tokens_padded
if enable_sp():
tp_size = get_tensor_model_parallel_world_size()
actual_tokens = num_tokens // tp_size
intermediate_tokens = (num_tokens_padded + tp_size - 1) // tp_size
if self.intermediate_tensors is None:
max_actual_tokens = self.max_num_tokens
if enable_sp():
max_actual_tokens = (self.max_num_tokens + tp_size - 1) // tp_size
self.intermediate_tensors = self.model.make_empty_intermediate_tensors(
batch_size=actual_tokens, dtype=self.dtype, device=self.device
batch_size=max_actual_tokens, dtype=self.dtype, device=self.device
)
intermediate_tensors = IntermediateTensors(
{k: v[:num_tokens_padded] for k, v in self.intermediate_tensors.items()}
{k: v[:intermediate_tokens] for k, v in self.intermediate_tensors.items()}
)
need_dummy_logits = not is_profile and lmhead_tp_enable()
@@ -485,21 +656,32 @@ class NPUGenerationModelRunner(OmniNPUModelRunner):
if hasattr(self.drafter, "model") and hasattr(self.drafter.model, "compute_logits"):
return self.drafter.model.compute_logits(hidden_states[dummy_indices])
model_kwargs = self._init_model_kwargs()
with set_ascend_forward_context(
attn_metadata,
self.vllm_config,
num_tokens=num_tokens_padded,
num_tokens_across_dp=num_tokens_across_dp,
in_profile_run=is_profile,
num_actual_tokens=0,
num_actual_tokens=num_tokens_padded,
aclgraph_runtime_mode=cudagraph_runtime_mode,
batch_descriptor=batch_descriptor,
batch_descriptor=batch_desc,
model_instance=self.model,
):
hidden_states = self._generate_dummy_run_hidden_states(
input_ids, positions, num_tokens_padded, intermediate_tensors, inputs_embeds
outputs = self.model(
input_ids=input_ids,
positions=positions,
intermediate_tensors=intermediate_tensors,
inputs_embeds=inputs_embeds,
**model_kwargs,
)
dummy_compute_logits(hidden_states)
if self.use_aux_hidden_state_outputs:
hidden_states, _ = outputs
else:
hidden_states = outputs
dummy_compute_logits(hidden_states)
if self.drafter:
self.drafter.dummy_run(
@@ -508,7 +690,7 @@ class NPUGenerationModelRunner(OmniNPUModelRunner):
num_reqs=num_reqs_padded,
num_tokens_across_dp=num_tokens_across_dp,
aclgraph_runtime_mode=cudagraph_runtime_mode,
batch_descriptor=batch_descriptor,
batch_descriptor=batch_desc,
dummy_compute_logits=dummy_drafter_compute_logits,
in_graph_capturing=not force_attention,
is_profile=is_profile,
@@ -518,30 +700,68 @@ class NPUGenerationModelRunner(OmniNPUModelRunner):
if self.dynamic_eplb:
self.eplb_updator.take_update_info_from_eplb_process()
self.eplb_updator.forward_end()
# -------------------------------------- Omni-new -------------------------------------------------
hidden_states, _ = self.extract_multimodal_outputs(hidden_states)
# -------------------------------------------------------------------------------------------------
return hidden_states
return hidden_states, None
def profile_run(self) -> None:
# Trigger compilation for general shape.
with self.set_in_profile_run():
hidden_states = self._dummy_run(
self.max_num_tokens // self.pcp_size if self.pcp_size > 1 else self.max_num_tokens, with_prefill=True
)
# MC2 will consume additional NPU memory.
# Therefore, we need to run the MC2 path once here to complete its initialization,
# allowing vLLM to correctly estimate the maximum memory required.
mc2_tokens_capacity = get_mc2_tokens_capacity()
if (
self.max_num_tokens > mc2_tokens_capacity
and self._select_moe_comm_method(mc2_tokens_capacity) == MoECommType.MC2
):
self._dummy_run(mc2_tokens_capacity, with_prefill=True)
# Profile with multimodal encoder & encoder cache.
if self.supports_mm_inputs:
mm_config = self.model_config.multimodal_config
if mm_config is not None and mm_config.skip_mm_profiling:
logger.info("Skipping memory profiling for multimodal encoder and encoder cache.")
else:
mm_budget = self.mm_budget
assert mm_budget is not None
if (encoder_budget := mm_budget.get_encoder_budget()) > 0:
if not mm_budget.mm_max_toks_per_item:
# All modality limits are 0 — embedding-only mode.
# Budget is non-zero for embedding storage, but
# there's no encoder to profile.
logger.info(
"Skipping encoder profiling for embedding-only "
"mode (all modality limits=0 with "
"enable_mm_embeds=True).",
)
else:
# NOTE: Currently model is profiled with a single non-text
# modality with the max possible input tokens even when
# it supports multiple.
dummy_modality = mm_budget.get_modality_with_max_tokens()
max_mm_items_per_batch = mm_budget.mm_max_items_per_batch[dummy_modality]
logger.info(
"Encoder cache will be initialized with a budget of "
"%s tokens, and profiled with %s %s items of the "
"maximum feature size.",
encoder_budget,
max_mm_items_per_batch,
dummy_modality,
)
# Create dummy batch of multimodal inputs.
batched_dummy_mm_inputs = self._get_mm_dummy_batch(
dummy_modality,
max_mm_items_per_batch,
)
# Run multimodal encoder.
dummy_encoder_outputs = self.model.embed_multimodal(**batched_dummy_mm_inputs)
sanity_check_mm_encoder_outputs(
dummy_encoder_outputs,
expected_num_items=max_mm_items_per_batch,
)
for i, output in enumerate(dummy_encoder_outputs):
self.encoder_cache[f"tmp_{i}"] = output
# Add `is_profile` here to pre-allocate communication buffers
hidden_states, _ = self._dummy_run(self.max_num_tokens, is_profile=True)
output = None
NPUPlatform.synchronize()
del hidden_states, output
self._sync_device()
del hidden_states
self.encoder_cache.clear()
gc.collect()
File diff suppressed because it is too large. Load diff