[Perf]: Speedup VoxCPM2 TTS performance and Support PagedAttention (#2690)

Signed-off-by: Sy03 <1370724210@qq.com>
Signed-off-by: Yueqian Lin <linyueqian@outlook.com>
Co-authored-by: Yueqian Lin <linyueqian@outlook.com>
Co-authored-by: Yueqian Lin <70319226+linyueqian@users.noreply.github.com>
This commit is contained in:
authored and GitHub committed 2026-04-14 09:16:47 +12:00
1 parent c9e2e3e8d7
commit 14f7910900
9 files changed
+1325 -440

No files matched your search

+3 -3
View File
@@ -58,12 +58,12 @@ The script accepts the following arguments:
## Performance
Measured on a single H20 GPU (80 GB), voxcpm 0.0.0, PyTorch 2.10.0+cu128:
Measured on a single H20 GPU (80 GB):
| Input length | RTF | Sample rate |
|---|---|---|
| Short (~6 words) | ~0.81 | 48 kHz |
| Long (~50 words) | ~0.72 | 48 kHz |
| Short (~10 tokens) | ~0.28 | 48 kHz |
| Long (~100 tokens) | ~0.34 | 48 kHz |
RTF < 1.0 means faster than real time.
@@ -71,10 +71,10 @@ def parse_args():
def extract_audio(multimodal_output: dict) -> torch.Tensor:
"""Extract the final complete audio tensor from multimodal output.
The output processor accumulates per-step full audio under ``audio``
as a list. The last element is the complete waveform.
The output processor concatenates per-step delta tensors under
``model_outputs``. Falls back to ``audio`` for backwards compat.
"""
audio = multimodal_output.get("audio") or multimodal_output.get("model_outputs")
audio = multimodal_output.get("model_outputs") or multimodal_output.get("audio")
if audio is None:
raise ValueError(f"No audio key in multimodal_output: {list(multimodal_output.keys())}")
@@ -49,12 +49,14 @@ _QWEN3_TTS_MODEL_STAGES = {"qwen3_tts"}
_FISH_TTS_MODEL_STAGES = {"fish_speech_slow_ar"}
_COSYVOICE3_TTS_MODEL_STAGES = {"cosyvoice3_talker"}
_OMNIVOICE_TTS_MODEL_STAGES = {"omnivoice_generator"}
_VOXCPM2_TTS_MODEL_STAGES = {"latent_generator"}
_TTS_MODEL_STAGES: set[str] = (
_VOXTRAL_TTS_MODEL_STAGES
| _QWEN3_TTS_MODEL_STAGES
| _FISH_TTS_MODEL_STAGES
| _COSYVOICE3_TTS_MODEL_STAGES
| _OMNIVOICE_TTS_MODEL_STAGES
| _VOXCPM2_TTS_MODEL_STAGES
)
_TTS_LANGUAGES: set[str] = {
"Auto",
@@ -290,6 +292,8 @@ class OmniOpenAIServingSpeech(OpenAIServing, AudioMixin):
return "cosyvoice3"
if model_stage in _OMNIVOICE_TTS_MODEL_STAGES:
return "omnivoice"
if model_stage in _VOXCPM2_TTS_MODEL_STAGES:
return "voxcpm2"
return None
def _compute_max_instructions_length(self) -> int:
@@ -787,6 +791,8 @@ class OmniOpenAIServingSpeech(OpenAIServing, AudioMixin):
return self._validate_fish_tts_request(request)
if self._tts_model_type == "cosyvoice3":
return self._validate_cosyvoice3_request(request)
if self._tts_model_type == "voxcpm2":
return None # VoxCPM2 accepts any text input
return self._validate_qwen_tts_request(request)
def _validate_ref_audio_format(self, ref_audio: str) -> str | None:
@@ -1430,6 +1436,15 @@ class OmniOpenAIServingSpeech(OpenAIServing, AudioMixin):
prompt["lang"] = request.language
if request.instructions:
prompt["instruct"] = request.instructions
elif self._tts_model_type == "voxcpm2":
tts_params = {}
additional: dict[str, Any] = {}
if request.ref_audio is not None:
wav_list, sr = await self._resolve_ref_audio(request.ref_audio)
additional["reference_audio"] = [[wav_list, sr]]
prompt = {"prompt": request.input}
if additional:
prompt["additional_information"] = additional
elif self._is_tts:
validation_error = self._validate_tts_request(request)
if validation_error:
@@ -1466,6 +1481,8 @@ class OmniOpenAIServingSpeech(OpenAIServing, AudioMixin):
model_type = "voxtral_tts"
elif self._tts_model_type == "cosyvoice3":
model_type = "cosyvoice3"
elif self._tts_model_type == "voxcpm2":
model_type = "voxcpm2"
elif self._is_tts:
model_type = tts_params.get("task_type", ["unknown"])[0]
else:
@@ -0,0 +1,114 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
"""fp32 RoPE + MLP matching native VoxCPM2 numerics.
Exports: _MiniCPMLongRoPE, _MiniCPMMLP, _apply_rotary_pos_emb
"""
from __future__ import annotations
import math
from typing import Any
import torch
import torch.nn as nn
import torch.nn.functional as F
# ===================================================================
# Primitives
# ===================================================================
def _rotate_half(x: torch.Tensor) -> torch.Tensor:
x1, x2 = x.chunk(2, dim=-1)
return torch.cat((-x2, x1), dim=-1)
def _apply_rotary_pos_emb(
q: torch.Tensor,
k: torch.Tensor,
cos: torch.Tensor,
sin: torch.Tensor,
) -> tuple[torch.Tensor, torch.Tensor]:
"""Apply rotary embeddings in float32."""
orig_dtype = q.dtype
q, k = q.to(torch.float32), k.to(torch.float32)
q_embed = (q * cos) + (_rotate_half(q) * sin)
k_embed = (k * cos) + (_rotate_half(k) * sin)
return q_embed.to(orig_dtype), k_embed.to(orig_dtype)
# ===================================================================
# LongRoPE — must match native computation order exactly
# ===================================================================
class _MiniCPMLongRoPE(nn.Module):
"""LongRoPE matching native computation order."""
def __init__(
self,
hidden_size: int,
num_attention_heads: int,
kv_channels: int | None,
rope_theta: float,
max_position_embeddings: int,
rope_scaling: dict[str, Any],
) -> None:
super().__init__()
self.dim = kv_channels if kv_channels else hidden_size // num_attention_heads
self.base = rope_theta
self.max_position_embeddings = max_position_embeddings
self.short_factor = rope_scaling["short_factor"]
self.long_factor = rope_scaling["long_factor"]
self.original_max_position_embeddings = rope_scaling["original_max_position_embeddings"]
scale = self.max_position_embeddings / self.original_max_position_embeddings
self.scaling_factor = math.sqrt(1 + math.log(scale) / math.log(self.original_max_position_embeddings))
inv_freq = 1.0 / (self.base ** (torch.arange(0, self.dim, 2).float() / self.dim))
self.register_buffer("inv_freq", inv_freq, persistent=False)
self.max_seq_len_cached = 0
self.register_buffer("cos_cached", torch.empty(0), persistent=False)
self.register_buffer("sin_cached", torch.empty(0), persistent=False)
self._set_cos_sin_cache(self.max_position_embeddings, self.inv_freq.device, torch.float32)
def _set_cos_sin_cache(self, seq_len: int, device: torch.device, dtype: torch.dtype) -> None:
self.max_seq_len_cached = seq_len
t = torch.arange(seq_len, device=device, dtype=self.inv_freq.dtype)
ext_factors = torch.tensor(
self.long_factor if seq_len > self.original_max_position_embeddings else self.short_factor,
dtype=torch.float32,
device=device,
)
freqs = torch.mul(
torch.outer(t, 1.0 / ext_factors).to(device=device),
self.inv_freq.to(device=device).to(dtype),
)
emb = torch.cat((freqs, freqs), dim=-1)
self.cos_cached = emb.cos().to(dtype) * self.scaling_factor
self.sin_cached = emb.sin().to(dtype) * self.scaling_factor
def forward(self, position_ids: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
return self.cos_cached[position_ids], self.sin_cached[position_ids]
# ===================================================================
# MLP
# ===================================================================
class _MiniCPMMLP(nn.Module):
"""SiLU-gated MLP matching native MiniCPMMLP."""
def __init__(self, hidden_size: int, intermediate_size: int) -> None:
super().__init__()
self.gate_proj = nn.Linear(hidden_size, intermediate_size, bias=False)
self.up_proj = nn.Linear(hidden_size, intermediate_size, bias=False)
self.down_proj = nn.Linear(intermediate_size, hidden_size, bias=False)
def forward(self, x: torch.Tensor) -> torch.Tensor:
return self.down_proj(F.silu(self.gate_proj(x)) * self.up_proj(x))
@@ -0,0 +1,448 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
"""MiniCPM4 with PagedAttention + fp32 RoPE/RMSNorm for VoxCPM2.
Uses vllm Attention for KV cache, keeps fp32 precision ops from
minicpm4_hf_compat.py to match native VoxCPM2 numerics.
"""
from __future__ import annotations
import math
from collections.abc import Iterable
from typing import Any
import torch
import torch.nn as nn
from vllm.config import CacheConfig, VllmConfig
from vllm.logger import init_logger
from vllm.model_executor.layers.attention import Attention
from vllm.model_executor.layers.layernorm import RMSNorm
from vllm.model_executor.model_loader.weight_utils import default_weight_loader
from vllm.model_executor.models.utils import make_empty_intermediate_tensors_factory
from vllm.sequence import IntermediateTensors
from .minicpm4_hf_compat import (
_apply_rotary_pos_emb,
_MiniCPMLongRoPE,
_MiniCPMMLP,
)
logger = init_logger(__name__)
def _resolve_lm_cfg(config: Any) -> Any:
"""Extract lm_config from VoxCPM2Config, converting dict to namespace if needed."""
lm_cfg = getattr(config, "lm_config", config)
if isinstance(lm_cfg, dict):
class _Cfg:
pass
c = _Cfg()
for k, v in lm_cfg.items():
setattr(c, k, v)
return c
return lm_cfg
# ===================================================================
# Attention with vllm PagedAttention backend
# ===================================================================
class _PagedMiniCPM4Attention(nn.Module):
"""PagedAttention + fp32 RoPE with separate q/k/v projections."""
def __init__(
self,
hidden_size: int,
num_attention_heads: int,
num_key_value_heads: int,
kv_channels: int | None,
layer_idx: int,
cache_config: CacheConfig | None = None,
prefix: str = "",
) -> None:
super().__init__()
self.layer_idx = layer_idx
self.hidden_size = hidden_size
self.num_heads = num_attention_heads
self.head_dim = kv_channels if kv_channels else hidden_size // num_attention_heads
self.num_kv_heads = num_key_value_heads
self.q_size = self.num_heads * self.head_dim
self.kv_size = self.num_kv_heads * self.head_dim
self.q_proj = nn.Linear(hidden_size, self.q_size, bias=False)
self.k_proj = nn.Linear(hidden_size, self.kv_size, bias=False)
self.v_proj = nn.Linear(hidden_size, self.kv_size, bias=False)
self.o_proj = nn.Linear(self.q_size, hidden_size, bias=False)
self._fused_qkv_weight: torch.Tensor | None = None
self.attn = Attention(
self.num_heads,
self.head_dim,
scale=self.head_dim**-0.5,
num_kv_heads=self.num_kv_heads,
cache_config=cache_config,
prefix=f"{prefix}.attn",
)
def forward(
self,
positions: torch.Tensor,
hidden_states: torch.Tensor,
rope_emb: _MiniCPMLongRoPE | None = None,
) -> torch.Tensor:
"""Forward: fused QKV → fp32 RoPE → PagedAttention → o_proj."""
if self._fused_qkv_weight is None:
self._fused_qkv_weight = torch.cat(
[
self.q_proj.weight,
self.k_proj.weight,
self.v_proj.weight,
],
dim=0,
).detach()
qkv = nn.functional.linear(hidden_states, self._fused_qkv_weight)
q, k, v = qkv.split([self.q_size, self.kv_size, self.kv_size], dim=-1)
if rope_emb is not None:
cos, sin = rope_emb(positions)
bsz = q.shape[0]
q_r = q.view(bsz, self.num_heads, self.head_dim)
k_r = k.view(bsz, self.num_kv_heads, self.head_dim)
q_r = q_r.unsqueeze(0).transpose(1, 2) # [1, heads, n_tokens, dim]
k_r = k_r.unsqueeze(0).transpose(1, 2) # [1, kv_heads, n_tokens, dim]
q_r, k_r = _apply_rotary_pos_emb(q_r, k_r, cos, sin)
q = q_r.transpose(1, 2).squeeze(0).reshape(bsz, -1) # [n_tokens, q_size]
k = k_r.transpose(1, 2).squeeze(0).reshape(bsz, -1) # [n_tokens, kv_size]
attn_output = self.attn(q, k, v)
output = self.o_proj(attn_output)
return output
# ===================================================================
# Decoder Layer
# ===================================================================
class _PagedMiniCPM4DecoderLayer(nn.Module):
"""Decoder layer: PagedAttention + fp32 RMSNorm + muP scale_depth."""
def __init__(
self,
hidden_size: int,
intermediate_size: int,
num_attention_heads: int,
num_key_value_heads: int,
kv_channels: int | None,
rms_norm_eps: float,
layer_idx: int,
num_hidden_layers: int,
use_mup: bool,
scale_depth: float,
cache_config: CacheConfig | None = None,
prefix: str = "",
) -> None:
super().__init__()
self.self_attn = _PagedMiniCPM4Attention(
hidden_size=hidden_size,
num_attention_heads=num_attention_heads,
num_key_value_heads=num_key_value_heads,
kv_channels=kv_channels,
layer_idx=layer_idx,
cache_config=cache_config,
prefix=f"{prefix}.self_attn",
)
self.mlp = _MiniCPMMLP(hidden_size, intermediate_size)
self.input_layernorm = RMSNorm(hidden_size, eps=rms_norm_eps)
self.post_attention_layernorm = RMSNorm(hidden_size, eps=rms_norm_eps)
self.use_mup = use_mup
self.scale_depth = scale_depth
self.num_hidden_layers = num_hidden_layers
def _residual_scale(self) -> float:
if self.use_mup:
return self.scale_depth / math.sqrt(self.num_hidden_layers)
return 1.0
def forward(
self,
positions: torch.Tensor,
hidden_states: torch.Tensor,
residual: torch.Tensor | None,
rope_emb: _MiniCPMLongRoPE | None = None,
) -> tuple[torch.Tensor, torch.Tensor | None]:
# Pre-norm + attention
residual = hidden_states
hidden_states = self.input_layernorm(hidden_states)
hidden_states = self.self_attn(positions, hidden_states, rope_emb)
scale = self._residual_scale()
if scale != 1.0:
hidden_states = residual + hidden_states * scale
else:
hidden_states = residual + hidden_states
# Pre-norm + FFN
residual = hidden_states
hidden_states = self.post_attention_layernorm(hidden_states)
hidden_states = self.mlp(hidden_states)
if scale != 1.0:
hidden_states = residual + hidden_states * scale
else:
hidden_states = residual + hidden_states
return hidden_states, None
# ===================================================================
# Full Model
# ===================================================================
class MiniCPM4PagedForVoxCPM2(nn.Module):
"""PagedAttention base_lm (28 layers) for VoxCPM2 scaffold."""
def __init__(self, *, vllm_config: VllmConfig, prefix: str = "") -> None:
super().__init__()
config = vllm_config.model_config.hf_config
cache_config = vllm_config.cache_config
self.config = config
lm_cfg = _resolve_lm_cfg(config)
hidden_size = lm_cfg.hidden_size
num_hidden_layers = lm_cfg.num_hidden_layers
kv_channels = getattr(lm_cfg, "kv_channels", None)
self.vocab_size = lm_cfg.vocab_size
self.embed_tokens = nn.Embedding(self.vocab_size, hidden_size)
rope_scaling = getattr(lm_cfg, "rope_scaling", None)
if isinstance(rope_scaling, dict):
rope_scaling_dict = rope_scaling
elif hasattr(rope_scaling, "__dict__"):
rope_scaling_dict = {
"short_factor": rope_scaling.short_factor,
"long_factor": rope_scaling.long_factor,
"original_max_position_embeddings": rope_scaling.original_max_position_embeddings,
}
else:
rope_scaling_dict = {}
no_rope = getattr(lm_cfg, "no_rope", False)
if not no_rope:
self.rope_emb = _MiniCPMLongRoPE(
hidden_size=hidden_size,
num_attention_heads=lm_cfg.num_attention_heads,
kv_channels=kv_channels,
rope_theta=getattr(lm_cfg, "rope_theta", 10000.0),
max_position_embeddings=getattr(lm_cfg, "max_position_embeddings", 32768),
rope_scaling=rope_scaling_dict,
)
else:
self.rope_emb = None
self.layers = nn.ModuleList(
[
_PagedMiniCPM4DecoderLayer(
hidden_size=hidden_size,
intermediate_size=lm_cfg.intermediate_size,
num_attention_heads=lm_cfg.num_attention_heads,
num_key_value_heads=lm_cfg.num_key_value_heads,
kv_channels=kv_channels,
rms_norm_eps=lm_cfg.rms_norm_eps,
layer_idx=i,
num_hidden_layers=num_hidden_layers,
use_mup=getattr(lm_cfg, "use_mup", False),
scale_depth=getattr(lm_cfg, "scale_depth", 1.0),
cache_config=cache_config,
prefix=f"{prefix}.layers.{i}",
)
for i in range(num_hidden_layers)
]
)
self.norm = RMSNorm(hidden_size, eps=lm_cfg.rms_norm_eps)
self.make_empty_intermediate_tensors = make_empty_intermediate_tensors_factory(
["hidden_states", "residual"], hidden_size
)
use_mup = getattr(lm_cfg, "use_mup", False)
self._scale_emb = getattr(lm_cfg, "scale_emb", 1.0) if use_mup else 1.0
self._compiled_layers: set[int] = set()
def embed_input_ids(self, input_ids: torch.Tensor, **_: Any) -> torch.Tensor:
return self.embed_tokens(input_ids) * self._scale_emb
def forward(
self,
input_ids: torch.Tensor | None,
positions: torch.Tensor,
intermediate_tensors: IntermediateTensors | None = None,
inputs_embeds: torch.Tensor | None = None,
**kwargs: Any,
) -> torch.Tensor | IntermediateTensors:
if inputs_embeds is not None:
hidden_states = inputs_embeds
else:
hidden_states = self.embed_input_ids(input_ids)
residual = None
for layer in self.layers:
hidden_states, residual = layer(
positions,
hidden_states,
residual,
self.rope_emb,
)
hidden_states = self.norm(hidden_states)
return hidden_states
def compile_selective(self) -> list[str]:
"""Compile MLP + o_proj; keep RMSNorm/RoPE eager for precision."""
compiled: list[str] = []
for i, layer in enumerate(self.layers):
if i in self._compiled_layers:
continue
try:
layer.mlp = torch.compile(
layer.mlp,
mode="default",
fullgraph=True,
)
layer.self_attn.o_proj = torch.compile(
layer.self_attn.o_proj,
mode="default",
fullgraph=True,
)
layer.self_attn._fused_qkv_weight = None
self._compiled_layers.add(i)
if i == 0:
compiled.append(f"layers.*.mlp (×{len(self.layers)})")
compiled.append(f"layers.*.self_attn.o_proj (×{len(self.layers)})")
except Exception as e:
logger.warning("compile_selective: layer %d failed: %s", i, e)
break
return compiled
def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
"""Load weights from native checkpoint (base_lm. prefix pre-stripped)."""
params_dict = dict(self.named_parameters(remove_duplicate=False))
loaded: set[str] = set()
for name, loaded_weight in weights:
if "rotary_emb.inv_freq" in name:
continue
param = params_dict.get(name)
if param is None:
continue
weight_loader = getattr(param, "weight_loader", default_weight_loader)
weight_loader(param, loaded_weight)
loaded.add(name)
return loaded
# ===================================================================
# Residual LM with PagedAttention (no RoPE, 8 layers)
# ===================================================================
class MiniCPM4PagedResidualLM(nn.Module):
"""PagedAttention residual LM (8 layers, no RoPE) for VoxCPM2."""
def __init__(self, *, vllm_config: VllmConfig, prefix: str = "") -> None:
super().__init__()
config = vllm_config.model_config.hf_config
cache_config = vllm_config.cache_config
self.config = config
lm_cfg = _resolve_lm_cfg(config)
hidden_size = lm_cfg.hidden_size
num_hidden_layers = getattr(config, "residual_lm_num_layers", 8)
kv_channels = getattr(lm_cfg, "kv_channels", None)
self.rope_emb = None
self.layers = nn.ModuleList(
[
_PagedMiniCPM4DecoderLayer(
hidden_size=hidden_size,
intermediate_size=lm_cfg.intermediate_size,
num_attention_heads=lm_cfg.num_attention_heads,
num_key_value_heads=lm_cfg.num_key_value_heads,
kv_channels=kv_channels,
rms_norm_eps=lm_cfg.rms_norm_eps,
layer_idx=i,
num_hidden_layers=num_hidden_layers,
use_mup=getattr(lm_cfg, "use_mup", False),
scale_depth=getattr(lm_cfg, "scale_depth", 1.0),
cache_config=cache_config,
prefix=f"{prefix}.layers.{i}",
)
for i in range(num_hidden_layers)
]
)
self.norm = RMSNorm(hidden_size, eps=lm_cfg.rms_norm_eps)
self._compiled_layers: set[int] = set()
def forward(
self,
positions: torch.Tensor,
inputs_embeds: torch.Tensor,
) -> torch.Tensor:
hidden_states = inputs_embeds
residual = None
for layer in self.layers:
hidden_states, residual = layer(
positions,
hidden_states,
residual,
self.rope_emb,
)
hidden_states = self.norm(hidden_states)
return hidden_states
def compile_selective(self) -> list[str]:
"""Compile MLP + o_proj (same as base_lm)."""
compiled: list[str] = []
for i, layer in enumerate(self.layers):
if i in self._compiled_layers:
continue
try:
layer.mlp = torch.compile(layer.mlp, mode="default", fullgraph=True)
layer.self_attn.o_proj = torch.compile(layer.self_attn.o_proj, mode="default", fullgraph=True)
layer.self_attn._fused_qkv_weight = None
self._compiled_layers.add(i)
if i == 0:
compiled.append(f"layers.*.mlp (×{len(self.layers)})")
compiled.append(f"layers.*.self_attn.o_proj (×{len(self.layers)})")
except Exception as e:
logger.warning("compile_selective: residual layer %d failed: %s", i, e)
return compiled
def load_weights_from_native(self, native_residual_lm: nn.Module) -> int:
"""Load weights from native residual_lm. Returns param count."""
params_dict = dict(self.named_parameters(remove_duplicate=False))
loaded = 0
for name, param in native_residual_lm.named_parameters():
if "rotary_emb" in name:
continue
target = params_dict.get(name)
if target is None:
continue
weight_loader = getattr(target, "weight_loader", default_weight_loader)
weight_loader(target, param.data)
loaded += 1
return loaded
File diff suppressed because it is too large. Load diff
@@ -1,13 +1,13 @@
# VoxCPM2 native AR single-stage pipeline.
# Uses native MiniCPM4 base_lm + native VAE decode in one stage.
# All computation (base_lm, residual_lm, diffusion, VAE) in forward().
# VoxCPM2 AR pipeline with per-request state batching.
# Uses native MiniCPM4 base_lm + per-request StaticKVCache.
# max_batch_size > 1 supported via KV cache save/restore.
stage_args:
- stage_id: 0
stage_type: llm
is_comprehension: true
runtime:
devices: "0"
max_batch_size: 1
max_batch_size: 4
engine_args:
dtype: bfloat16
model_stage: latent_generator
+12 -5
View File
@@ -262,6 +262,10 @@ class GPUARModelRunner(OmniGPUModelRunner, OmniConnectorModelRunnerMixin):
# Update persistent batch states.
deferred_state_corrections_fn = self._update_states(scheduler_output)
# Notify model of finished requests for state cleanup
if scheduler_output.finished_req_ids and hasattr(self.model, "on_requests_finished"):
self.model.on_requests_finished(scheduler_output.finished_req_ids)
if has_ec_transfer() and not get_ec_transfer().is_consumer:
with self.maybe_get_ec_connector_output(
scheduler_output,
@@ -793,11 +797,14 @@ class GPUARModelRunner(OmniGPUModelRunner, OmniConnectorModelRunnerMixin):
elif isinstance(v, dict):
mm_payload[k] = {sk: sv[start:end].contiguous() for sk, sv in v.items()}
elif isinstance(v, list):
element = v[idx] if idx < len(v) else v[0]
# Clone tensors to avoid cross-request aliasing
if isinstance(element, torch.Tensor):
element = element.clone()
mm_payload[k] = element
if idx < len(v):
element = v[idx]
if element is not None:
if isinstance(element, torch.Tensor):
element = element.clone()
mm_payload[k] = element
# Skip None elements: msgspec cannot serialize None
# in dict[str, torch.Tensor] typed fields.
elif isinstance(v, torch.Tensor):
# List-derived tensor payloads are request-invariant; clone to
# avoid accidental cross-request aliasing on downstream mutation.
+1
View File
@@ -1241,6 +1241,7 @@ class OmniGPUModelRunner(GPUModelRunner):
span_len = int(e) - int(s)
# call the custom process function
req_infos["request_id"] = req_id
embed_slice = inputs_embeds[s:e] if inputs_embeds is not None else None
req_input_ids, req_embeds, update_dict = self.model.preprocess(
input_ids=input_ids[s:e], input_embeds=embed_slice, **req_infos