@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / e7fc70016f
Fork 0

A high-throughput and memory-efficient inference and serving engine for LLMs

Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git

Synced Nov 14, 05:30 UTC

[Model] Add MiDashengLM model support (#23652)

Signed-off-by: chenbing8 <chenbing8@xiaomi.com> Signed-off-by: bingchen-mi <chenbing8@xiaomi.com> Co-authored-by: Jee Jee Li <pandaleefree@gmail.com> Co-authored-by: Isotr0py <mozf@mail2.sysu.edu.cn>

bingchen-mi committed Sep 4, 2025 at 15:08 UTC e7fc70016fed270940759dc753a726790701a0c1
9 files changed +928
docs/models/supported_models.md
+1
@@ -643,6 +643,7 @@ These models primarily accept the [`LLM.generate`](./generative_models.md#llmgen
643 | `LlavaNextForConditionalGeneration` | LLaVA-NeXT | T + I<sup>E+</sup> | `llava-hf/llava-v1.6-mistral-7b-hf`, `llava-hf/llava-v1.6-vicuna-7b-hf`, etc. | | ✅︎ | ✅︎ |
644 | `LlavaNextVideoForConditionalGeneration` | LLaVA-NeXT-Video | T + V | `llava-hf/LLaVA-NeXT-Video-7B-hf`, etc. | | ✅︎ | ✅︎ |
645 | `LlavaOnevisionForConditionalGeneration` | LLaVA-Onevision | T + I<sup>+</sup> + V<sup>+</sup> | `llava-hf/llava-onevision-qwen2-7b-ov-hf`, `llava-hf/llava-onevision-qwen2-0.5b-ov-hf`, etc. | | ✅︎ | ✅︎ |
646 +| `MiDashengLMModel` | MiDashengLM | T + A<sup>+</sup> | `mispeech/midashenglm-7b` | | ✅︎ | ✅︎ |
647 | `MiniCPMO` | MiniCPM-O | T + I<sup>E+</sup> + V<sup>E+</sup> + A<sup>E+</sup> | `openbmb/MiniCPM-o-2_6`, etc. | ✅︎ | ✅︎ | ✅︎ |
648 | `MiniCPMV` | MiniCPM-V | T + I<sup>E+</sup> + V<sup>E+</sup> | `openbmb/MiniCPM-V-2` (see note), `openbmb/MiniCPM-Llama3-V-2_5`, `openbmb/MiniCPM-V-2_6`, `openbmb/MiniCPM-V-4`, `openbmb/MiniCPM-V-4_5`, etc. | ✅︎ | | ✅︎ |
649 | `MiniMaxVL01ForConditionalGeneration` | MiniMax-VL | T + I<sup>E+</sup> | `MiniMaxAI/MiniMax-VL-01`, etc. | | ✅︎ | ✅︎ |
examples/offline_inference/audio_language.py
+31
@@ -146,6 +146,36 @@ def run_granite_speech(question: str, audio_count: int) -> ModelRequestData:
146 )
147
148
149 +# MiDashengLM
150 +def run_midashenglm(question: str, audio_count: int):
151 + model_name = "mispeech/midashenglm-7b"
152 +
153 + engine_args = EngineArgs(
154 + model=model_name,
155 + trust_remote_code=True,
156 + max_model_len=4096,
157 + max_num_seqs=5,
158 + limit_mm_per_prompt={"audio": audio_count},
159 + )
160 +
161 + audio_in_prompt = "".join(
162 + ["<|audio_bos|><|AUDIO|><|audio_eos|>" for idx in range(audio_count)]
163 + )
164 +
165 + default_system = "You are a helpful language and speech assistant."
166 +
167 + prompt = (
168 + f"<|im_start|>system\n{default_system}<|im_end|>\n"
169 + "<|im_start|>user\n"
170 + f"{audio_in_prompt}{question}<|im_end|>\n"
171 + "<|im_start|>assistant\n"
172 + )
173 + return ModelRequestData(
174 + engine_args=engine_args,
175 + prompt=prompt,
176 + )
177 +
178 +
179 # MiniCPM-O
180 def run_minicpmo(question: str, audio_count: int) -> ModelRequestData:
181 model_name = "openbmb/MiniCPM-o-2_6"
@@ -352,6 +382,7 @@ model_example_map = {
382 "voxtral": run_voxtral,
383 "gemma3n": run_gemma3n,
384 "granite_speech": run_granite_speech,
385 + "midashenglm": run_midashenglm,
386 "minicpmo": run_minicpmo,
387 "phi4_mm": run_phi4mm,
388 "phi4_multimodal": run_phi4_multimodal,
tests/models/multimodal/processing/test_common.py
+1
@@ -302,6 +302,7 @@ def _test_processing_correctness_one(
302 "llava-hf/llava-onevision-qwen2-0.5b-ov-hf",
303 "meta-llama/Llama-3.2-11B-Vision-Instruct",
304 "TIGER-Lab/Mantis-8B-siglip-llama3",
305 + "mispeech/midashenglm-7b",
306 "openbmb/MiniCPM-Llama3-V-2_5",
307 "openbmb/MiniCPM-o-2_6",
308 "openbmb/MiniCPM-V-2_6",
tests/models/registry.py
+2
@@ -460,6 +460,8 @@ _MULTIMODAL_EXAMPLE_MODELS = {
460 max_transformers_version="4.48", # noqa: E501
461 transformers_version_reason="HF model is not compatible.", # noqa: E501
462 hf_overrides={"architectures": ["MantisForConditionalGeneration"]}), # noqa: E501
463 + "MiDashengLMModel": _HfExamplesInfo("mispeech/midashenglm-7b",
464 + trust_remote_code=True),
465 "MiniCPMO": _HfExamplesInfo("openbmb/MiniCPM-o-2_6",
466 trust_remote_code=True),
467 "MiniCPMV": _HfExamplesInfo("openbmb/MiniCPM-Llama3-V-2_5",
vllm/model_executor/models/midashenglm.py new
+788
@@ -0,0 +1,788 @@
1 +# SPDX-License-Identifier: Apache-2.0
2 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 +# Copyright 2025 Horizon team, Xiaomi MiLM Plus.
4 +# Copyright 2024 The Qwen team.
5 +# Copyright 2023 The vLLM team.
6 +# Copyright 2022 EleutherAI and the HuggingFace Inc. team. All rights reserved.
7 +#
8 +# This code is based on EleutherAI's GPT-NeoX library and the GPT-NeoX
9 +# and OPT implementations in this library. It has been modified from its
10 +# original forms to accommodate minor architectural differences compared
11 +# to GPT-NeoX and OPT used by the Meta AI team that trained the model.
12 +#
13 +# Licensed under the Apache License, Version 2.0 (the "License");
14 +# you may not use this file except in compliance with the License.
15 +# You may obtain a copy of the License at
16 +#
17 +# http://www.apache.org/licenses/LICENSE-2.0
18 +#
19 +# Unless required by applicable law or agreed to in writing, software
20 +# distributed under the License is distributed on an "AS IS" BASIS,
21 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22 +# See the License for the specific language governing permissions and
23 +# limitations under the License.
24 +"""Inference-only MiDashengLM model compatible with HuggingFace weights."""
25 +import collections
26 +import collections.abc
27 +from collections.abc import Iterable, Mapping, Sequence
28 +from typing import Any, Callable, Optional, TypedDict, Union, cast
29 +
30 +import numpy as np
31 +import torch
32 +import torch.nn as nn
33 +import torchaudio.transforms as audio_transforms
34 +from transformers import BatchFeature
35 +
36 +from vllm.attention.layer import MultiHeadAttention
37 +from vllm.config import VllmConfig
38 +from vllm.distributed import get_tensor_model_parallel_world_size
39 +from vllm.model_executor.layers.activation import get_act_fn
40 +from vllm.model_executor.layers.linear import (ColumnParallelLinear,
41 + QKVParallelLinear,
42 + RowParallelLinear)
43 +from vllm.model_executor.layers.quantization import QuantizationConfig
44 +from vllm.model_executor.model_loader.utils import set_default_torch_dtype
45 +from vllm.model_executor.sampling_metadata import SamplingMetadata
46 +from vllm.multimodal import MULTIMODAL_REGISTRY
47 +from vllm.multimodal.inputs import (MultiModalDataDict, MultiModalFieldConfig,
48 + MultiModalKwargsItems)
49 +from vllm.multimodal.parse import MultiModalDataItems, MultiModalDataParser
50 +from vllm.multimodal.processing import (BaseMultiModalProcessor,
51 + BaseProcessingInfo, PromptReplacement,
52 + PromptUpdate, PromptUpdateDetails)
53 +from vllm.multimodal.profiling import BaseDummyInputsBuilder
54 +from vllm.sequence import IntermediateTensors
55 +from vllm.transformers_utils.configs.midashenglm import DashengConfig
56 +
57 +from .interfaces import MultiModalEmbeddings, SupportsMultiModal, SupportsPP
58 +from .utils import (AutoWeightsLoader, init_vllm_registered_model,
59 + maybe_prefix, merge_multimodal_embeddings)
60 +
61 +_Tuple2 = Union[int, tuple[int, int], Sequence[int]]
62 +
63 +
64 +def _resolve_tuple2(x: _Tuple2) -> tuple[int, int]:
65 + if isinstance(x, collections.abc.Sequence):
66 + assert len(x) == 2, (
67 + f"Expected a sequence of length 2, got {x} with length {len(x)}")
68 + return cast(tuple[int, int], tuple(x))
69 + return (x, x)
70 +
71 +
72 +def calculate_mel_frames_dasheng(
73 + audio_length_samples: int,
74 + n_fft: int = 512,
75 + hop_size: int = 160,
76 + dasheng_subsampling: int = 4,
77 + center=True,
78 + model_subsampling: int = 5,
79 +) -> int:
80 + """Calculate the number of Mel-spectrogram frames."""
81 + if center:
82 + audio_length_samples = audio_length_samples + n_fft
83 +
84 + return (int(1 + ((audio_length_samples - n_fft) / hop_size)) //
85 + dasheng_subsampling // model_subsampling)
86 +
87 +
88 +class AudioPatchEmbed(nn.Module):
89 +
90 + def __init__(
91 + self,
92 + input_size: _Tuple2 = 64,
93 + patch_size: _Tuple2 = 16,
94 + patch_stride: _Tuple2 = 16,
95 + in_chans: int = 1,
96 + embed_dim: int = 768,
97 + norm_layer: Optional[Callable] = None,
98 + flatten: bool = False,
99 + ):
100 + super().__init__()
101 + self.input_size = _resolve_tuple2(input_size)
102 + self.patch_size = _resolve_tuple2(patch_size)
103 + self.patch_stride = _resolve_tuple2(patch_stride)
104 + self.grid_size = (
105 + self.input_size[0] // self.patch_stride[0],
106 + self.input_size[1] // self.patch_stride[1],
107 + )
108 + self.num_patches = self.grid_size[0] * self.grid_size[1]
109 + self.flatten = flatten
110 +
111 + self.proj = nn.Conv2d(
112 + in_chans,
113 + embed_dim,
114 + kernel_size=self.patch_size,
115 + stride=self.patch_stride,
116 + )
117 + self.norm = norm_layer(embed_dim) if norm_layer else nn.Identity()
118 +
119 + def forward(self, x: torch.Tensor) -> torch.Tensor:
120 + x = self.proj(x)
121 + if self.flatten:
122 + x = torch.permute(torch.flatten(
123 + x, 2, 3), (0, 2, 1)) # rearrange(x, "b c f t -> b (f t) c")
124 + x = self.norm(x)
125 + return x
126 +
127 +
128 +class LayerScale(nn.Module):
129 +
130 + def __init__(self, dim, init_values=1e-5, inplace=False):
131 + super().__init__()
132 + self.inplace = inplace
133 + self.gamma = nn.Parameter(init_values * torch.ones(dim))
134 +
135 + def forward(self, x: torch.Tensor) -> torch.Tensor:
136 + return x.mul_(self.gamma) if self.inplace else x * self.gamma
137 +
138 +
139 +class DashengMlp(nn.Module):
140 +
141 + def __init__(
142 + self,
143 + in_features: int,
144 + hidden_features: Optional[int] = None,
145 + out_features: Optional[int] = None,
146 + quant_config: Optional[QuantizationConfig] = None,
147 + prefix: str = "",
148 + ):
149 + super().__init__()
150 + out_features = out_features or in_features
151 + hidden_features = hidden_features or in_features
152 + self.fc1 = ColumnParallelLinear(input_size=in_features,
153 + output_size=hidden_features,
154 + quant_config=quant_config,
155 + prefix=f"{prefix}.fc1")
156 + self.act = get_act_fn("gelu")
157 + self.fc2 = RowParallelLinear(input_size=hidden_features,
158 + output_size=out_features,
159 + quant_config=quant_config,
160 + prefix=f"{prefix}.fc2")
161 +
162 + def forward(self, x: torch.Tensor) -> torch.Tensor:
163 + x, _ = self.fc1(x)
164 + x = self.act(x)
165 + x, _ = self.fc2(x)
166 + return x
167 +
168 +
169 +class DashengAttention(nn.Module):
170 +
171 + def __init__(
172 + self,
173 + dim: int,
174 + num_heads: int = 8,
175 + qkv_bias: bool = False,
176 + causal: bool = False,
177 + quant_config: Optional[QuantizationConfig] = None,
178 + prefix: str = "",
179 + ):
180 + super().__init__()
181 + assert dim % num_heads == 0, "dim should be divisible by num_heads"
182 + self.embed_dim = dim
183 + tp_size = get_tensor_model_parallel_world_size()
184 + self.total_num_heads = num_heads
185 + assert self.total_num_heads % tp_size == 0
186 + self.num_heads = self.total_num_heads // tp_size
187 + if self.total_num_heads >= tp_size:
188 + # Number of heads is greater than TP size, so we partition
189 + # the KV heads across multiple tensor parallel GPUs.
190 + assert self.total_num_heads % tp_size == 0
191 + else:
192 + # Number of heads is less than TP size, so we replicate
193 + # the KV heads across multiple tensor parallel GPUs.
194 + assert tp_size % self.total_num_heads == 0
195 + self.num_kv_heads = max(1, self.total_num_heads // tp_size)
196 + self.head_dim = self.embed_dim // self.total_num_heads
197 + self.q_size = self.num_heads * self.head_dim
198 + self.kv_size = self.num_kv_heads * self.head_dim
199 + self.scale = self.head_dim**-0.5
200 +
201 + self.qkv = QKVParallelLinear(
202 + hidden_size=self.embed_dim,
203 + head_size=self.head_dim,
204 + total_num_heads=self.total_num_heads,
205 + total_num_kv_heads=self.total_num_heads,
206 + bias=qkv_bias,
207 + quant_config=quant_config,
208 + prefix=f"{prefix}.qkv",
209 + )
210 + self.attn = MultiHeadAttention(
211 + self.num_heads,
212 + self.head_dim,
213 + self.scale,
214 + num_kv_heads=self.num_kv_heads,
215 + )
216 + self.proj = RowParallelLinear(
217 + input_size=dim,
218 + output_size=dim,
219 + quant_config=quant_config,
220 + prefix=f"{prefix}.proj",
221 + )
222 + self.causal = causal
223 +
224 + def forward(self, x: torch.Tensor, mask: Optional[torch.Tensor] = None):
225 + B, N, C = x.shape
226 +
227 + qkv_out, _ = self.qkv(x)
228 + q, k, v = qkv_out.split([self.q_size, self.kv_size, self.kv_size],
229 + dim=-1)
230 +
231 + attn_out = self.attn(q, k, v)
232 + C_local = attn_out.numel() // (B * N) # C_local for parallel
233 + attn_out = attn_out.view(B, N, C_local)
234 +
235 + x, _ = self.proj(attn_out)
236 +
237 + return x
238 +
239 +
240 +class DashengBlock(nn.Module):
241 +
242 + def __init__(
243 + self,
244 + dim: int,
245 + num_heads: int,
246 + mlp_ratio: float = 4.0,
247 + qkv_bias: bool = False,
248 + init_values: Optional[float] = None,
249 + quant_config: Optional[QuantizationConfig] = None,
250 + prefix: str = "",
251 + ):
252 + super().__init__()
253 + self.norm1 = nn.LayerNorm(dim, eps=1e-6)
254 + self.attn = DashengAttention(
255 + dim,
256 + num_heads=num_heads,
257 + qkv_bias=qkv_bias,
258 + quant_config=quant_config,
259 + prefix=f"{prefix}.attn",
260 + )
261 + self.ls1 = (LayerScale(dim, init_values=init_values)
262 + if init_values else nn.Identity())
263 +
264 + self.norm2 = nn.LayerNorm(dim, eps=1e-6)
265 + self.mlp = DashengMlp(
266 + in_features=dim,
267 + hidden_features=int(dim * mlp_ratio),
268 + quant_config=quant_config,
269 + prefix=f"{prefix}.mlp",
270 + )
271 + self.ls2 = (LayerScale(dim, init_values=init_values)
272 + if init_values else nn.Identity())
273 +
274 + # Kwargs usually has a mask parameter that is passed to Attention
275 + def forward(
276 + self,
277 + x: torch.Tensor,
278 + mask: Optional[torch.Tensor] = None,
279 + ) -> torch.Tensor:
280 + x = x + self.ls1(self.attn(self.norm1(x), mask))
281 + x = x + self.ls2(self.mlp(self.norm2(x)))
282 + return x
283 +
284 +
285 +class DashengAudioTransformer(nn.Module):
286 +
287 + def __init__(
288 + self,
289 + config: DashengConfig,
290 + quant_config: Optional[QuantizationConfig] = None,
291 + prefix: str = "",
292 + ):
293 + super().__init__()
294 +
295 + self.target_length = config.target_length
296 + self.hop_length = config.hop_length
297 +
298 + self._init_front_end(config)
299 +
300 + self.init_bn = nn.BatchNorm2d(config.n_mels, momentum=0.01)
301 +
302 + self.patch_embed = AudioPatchEmbed(
303 + input_size=(config.n_mels, config.target_length),
304 + embed_dim=config.embed_dim,
305 + in_chans=config.input_channels,
306 + patch_size=config.patch_size,
307 + flatten=False,
308 + patch_stride=config.patch_stride,
309 + )
310 +
311 + self.time_pos_embed = nn.Parameter(
312 + torch.empty(1, config.embed_dim, 1, self.patch_embed.grid_size[1]))
313 + self.freq_pos_embed = nn.Parameter(
314 + torch.empty(1, config.embed_dim, self.patch_embed.grid_size[0], 1))
315 + self.blocks = nn.ModuleList(
316 + DashengBlock(
317 + dim=config.embed_dim,
318 + num_heads=config.num_heads,
319 + mlp_ratio=config.mlp_ratio,
320 + qkv_bias=config.qkv_bias,
321 + init_values=config.init_values,
322 + quant_config=quant_config,
323 + prefix=f"{prefix}.block{i}",
324 + ) for i in range(config.depth))
325 + self.norm = nn.LayerNorm(config.embed_dim, eps=1e-6)
326 +
327 + def _init_front_end(self, config):
328 + with set_default_torch_dtype(torch.float32):
329 + self.front_end = nn.Sequential(
330 + audio_transforms.MelSpectrogram(
331 + f_min=config.f_min,
332 + f_max=config.f_max,
333 + center=config.center,
334 + win_length=config.win_length,
335 + hop_length=config.hop_length,
336 + sample_rate=config.sample_rate,
337 + n_fft=config.n_fft,
338 + n_mels=config.n_mels,
339 + ),
340 + audio_transforms.AmplitudeToDB(top_db=120),
341 + )
342 +
343 + mel_spectrogram = self.front_end[0]
344 + fb = mel_spectrogram.mel_scale.fb
345 + win = mel_spectrogram.spectrogram.window
346 + mel_spectrogram.mel_scale.fb = fb.to(torch.bfloat16).to(
347 + torch.float32)
348 + mel_spectrogram.spectrogram.window = win.to(torch.bfloat16).to(
349 + torch.float32)
350 +
351 + def forward_features(
352 + self,
353 + x: torch.Tensor,
354 + mask: Optional[torch.Tensor] = None,
355 + ) -> torch.Tensor:
356 + t = x.shape[-1]
357 + x = x + self.time_pos_embed[:, :, :, :t]
358 + x = (x + self.freq_pos_embed[:, :, :, :]
359 + ) # Just to support __getitem__ in posembed
360 + x = torch.permute(torch.flatten(x, 2, 3),
361 + (0, 2, 1)) # rearrange(x, "b c f t -> b (f t) c")
362 + for block in self.blocks:
363 + x = block(x, mask)
364 + x = self.norm(x)
365 + return x
366 +
367 + def _to_mask(self, lengths: torch.Tensor, max_length: int) -> torch.Tensor:
368 + batch_size = len(lengths)
369 + idx = torch.arange(max_length, device=lengths.device)
370 + idx = idx.repeat(batch_size).view(batch_size, max_length)
371 + mask = (idx < lengths.unsqueeze(-1)).bool()
372 + return mask
373 +
374 + def forward(
375 + self,
376 + x: torch.Tensor,
377 + x_length: Optional[torch.Tensor] = None,
378 + ) -> tuple[torch.Tensor, Optional[torch.Tensor]]:
379 + x = self.front_end(x)
380 + x = x.to(self.time_pos_embed.dtype)
381 + target_length_in_patches = self.target_length // 4
382 + x = x.unsqueeze(1)
383 + x = torch.permute(x, (0, 2, 1, 3))
384 + x = self.init_bn(x)
385 + x = torch.permute(x, (0, 2, 1, 3))
386 +
387 + x = self.patch_embed(x)
388 + t = x.shape[-1]
389 +
390 + input_splits = x.split(target_length_in_patches, dim=-1)
391 +
392 + if x_length is not None:
393 + assert len(x_length) == len(x), (
394 + "batchsizes of input x and x_length need to be same")
395 + assert x_length.ndim == 1, "Lengths are of size (B,)"
396 + scaled_lengths = (x_length / (self.hop_length * 4)).long()
397 + mask = self._to_mask(max_length=t, lengths=scaled_lengths)
398 + split_masks = mask.logical_not().split(target_length_in_patches,
399 + dim=-1)
400 + else:
401 + mask = None
402 + split_masks = [None] * len(input_splits)
403 +
404 + outputs = []
405 +
406 + for split_x, split_mask in zip(input_splits, split_masks):
407 + forward_kwargs = {}
408 + forward_kwargs["mask"] = split_mask
409 + split_x = self.forward_features(split_x, **forward_kwargs)
410 + outputs.append(split_x)
411 + x = torch.cat(outputs, dim=1)
412 + return x, mask
413 +
414 +
415 +class AudioProjectorSubsample(nn.Module):
416 +
417 + def __init__(
418 + self,
419 + in_dim: int,
420 + out_dim: int,
421 + downsample_rate=5,
422 + dtype: Optional[torch.dtype] = None,
423 + quant_config: Optional[QuantizationConfig] = None,
424 + prefix: str = "",
425 + ):
426 + super().__init__()
427 + self.k = downsample_rate
428 + self.net = nn.Sequential(
429 + ColumnParallelLinear(
430 + input_size=in_dim * self.k,
431 + output_size=out_dim,
432 + quant_config=quant_config,
433 + prefix=f"{prefix}.net.0",
434 + return_bias=False,
435 + ), get_act_fn("gelu"),
436 + RowParallelLinear(
437 + input_size=out_dim,
438 + output_size=out_dim,
439 + quant_config=quant_config,
440 + prefix=f"{prefix}.net.2",
441 + return_bias=False,
442 + ))
443 +
444 + def forward(self, x, mask=None):
445 + batch_size, seq_len, dim = x.shape
446 + num_frames_to_discard = seq_len % self.k
447 + if num_frames_to_discard > 0:
448 + x = x[:, :-num_frames_to_discard, :]
449 + if mask is not None:
450 + mask = mask[:, :-num_frames_to_discard]
451 + if mask is None:
452 + mask = torch.ones(x.shape[:-1], dtype=torch.long, device=x.device)
453 + x = x.reshape(batch_size, -1, self.k *
454 + dim) # rearrange(x, "b (s k) d -> b s (k d)", k=self.k)
455 + for layer in self.net:
456 + x = layer(x)
457 + mask = mask.reshape(
458 + batch_size, -1,
459 + self.k) # rearrange(mask, "b (s k) -> b s k", k=self.k)
460 + mask = mask.any(dim=-1).long()
461 + return x, mask
462 +
463 +
464 +# === Audio Inputs === #
465 +class MiDashengLMAudioInputs(TypedDict):
466 + input_values: torch.Tensor
467 + """Shape: `(num_audios, num_sampling_points)`"""
468 + audio_length: torch.Tensor
469 + """Shape: `(num_audios, 1)`"""
470 +
471 +
472 +class MiDashengLMProcessingInfo(BaseProcessingInfo):
473 +
474 + def get_hf_config(self):
475 + return self.ctx.get_hf_config()
476 +
477 + def get_feature_extractor(self):
478 + hf_processor = self.get_hf_processor()
479 + feature_extractor = hf_processor.feature_extractor
480 + return feature_extractor
481 +
482 + def get_supported_mm_limits(self) -> Mapping[str, Optional[int]]:
483 + return {"audio": None}
484 +
485 + def get_min_audio_len(self):
486 + return 3200
487 +
488 + def get_max_audio_len(self):
489 + return 160000
490 +
491 +
492 +class MiDashengLMDummyInputsBuilder(
493 + BaseDummyInputsBuilder[MiDashengLMProcessingInfo]):
494 +
495 + def get_dummy_text(self, mm_counts: Mapping[str, int]) -> str:
496 + num_audios = mm_counts.get("audio", 0)
497 +
498 + hf_processor = self.info.get_hf_processor()
499 + audio_token = hf_processor.audio_token
500 +
501 + return audio_token * num_audios
502 +
503 + def get_dummy_mm_data(
504 + self,
505 + seq_len: int,
506 + mm_counts: Mapping[str, int],
507 + ) -> MultiModalDataDict:
508 + num_audios = mm_counts.get("audio", 0)
509 +
510 + return {
511 + "audio":
512 + self._get_dummy_audios(length=self.info.get_max_audio_len(),
513 + num_audios=num_audios)
514 + }
515 +
516 +
517 +class MiDashengLMMultiModalProcessor(
518 + BaseMultiModalProcessor[MiDashengLMProcessingInfo]):
519 +
520 + def _get_data_parser(self) -> MultiModalDataParser:
521 + feature_extractor = self.info.get_feature_extractor()
522 + return MultiModalDataParser(target_sr=feature_extractor.sampling_rate)
523 +
524 + def _call_hf_processor(
525 + self,
526 + prompt: str,
527 + mm_data: Mapping[str, object],
528 + mm_kwargs: Mapping[str, Any],
529 + tok_kwargs: Mapping[str, object],
530 + ) -> BatchFeature:
531 + audios = mm_data.pop("audios", [])
532 +
533 + # + Padding
534 + min_audio_len = self.info.get_min_audio_len()
535 + processed_audios = [
536 + np.pad(audio, (0, min_audio_len - audio.shape[-1]),
537 + mode='constant',
538 + constant_values=0) if isinstance(audio, np.ndarray)
539 + and audio.shape[-1] < min_audio_len else audio for audio in audios
540 + ]
541 +
542 + if processed_audios:
543 + mm_data["audio"] = processed_audios
544 +
545 + if not mm_data.get("audio", []):
546 + prompt_ids = self.info.get_tokenizer().encode(prompt)
547 + prompt_ids = self._apply_hf_processor_tokens_only(prompt_ids)
548 + return BatchFeature(dict(input_ids=[prompt_ids]), tensor_type="pt")
549 +
550 + mm_kwargs = dict(**mm_kwargs, )
551 +
552 + return super()._call_hf_processor(
553 + prompt=prompt,
554 + mm_data=mm_data,
555 + mm_kwargs=mm_kwargs,
556 + tok_kwargs=tok_kwargs,
557 + )
558 +
559 + def _get_mm_fields_config(
560 + self,
561 + hf_inputs: BatchFeature,
562 + hf_processor_mm_kwargs: Mapping[str, object],
563 + ) -> Mapping[str, MultiModalFieldConfig]:
564 + return dict(
565 + input_values=MultiModalFieldConfig.batched("audio"),
566 + audio_length=MultiModalFieldConfig.batched("audio"),
567 + )
568 +
569 + def _get_prompt_updates(
570 + self,
571 + mm_items: MultiModalDataItems,
572 + hf_processor_mm_kwargs: Mapping[str, object],
573 + out_mm_kwargs: MultiModalKwargsItems,
574 + ) -> Sequence[PromptUpdate]:
575 + processor = self.info.get_hf_processor(**hf_processor_mm_kwargs)
576 + tokenizer = self.info.get_tokenizer()
577 + vocab = tokenizer.get_vocab()
578 +
579 + audio_token = getattr(processor, "audio_token", "<|AUDIO|>")
580 + audio_bos_token = getattr(processor, "audio_bos_token",
581 + "<|audio_bos|>")
582 + audio_eos_token = getattr(processor, "audio_eos_token",
583 + "<|audio_eos|>")
584 +
585 + audio_token_id = vocab[audio_token]
586 + audio_bos_id = vocab[audio_bos_token]
587 + audio_eos_id = vocab[audio_eos_token]
588 +
589 + out_mm_data = out_mm_kwargs.get_data()
590 + audio_length = out_mm_data.get("audio_length")
591 + if audio_length is None:
592 + audio_output_lengths = []
593 + else:
594 + audio_length_np = audio_length.cpu().numpy() if isinstance(
595 + audio_length, torch.Tensor) else audio_length
596 + audio_output_lengths = [
597 + max(1, calculate_mel_frames_dasheng(
598 + int(length))) # at least one frame
599 + for length in audio_length_np
600 + ]
601 +
602 + def get_replacement_midashenglm(item_idx: int):
603 + num_features = audio_output_lengths[item_idx]
604 + audio_tokens = [audio_token_id] * num_features
605 +
606 + return PromptUpdateDetails.select_token_id(
607 + [audio_bos_id] + audio_tokens + [audio_eos_id],
608 + embed_token_id=audio_token_id,
609 + )
610 +
611 + return [
612 + PromptReplacement(
613 + modality="audio",
614 + target=audio_token,
615 + replacement=get_replacement_midashenglm,
616 + )
617 + ]
618 +
619 +
620 +@MULTIMODAL_REGISTRY.register_processor(
621 + MiDashengLMMultiModalProcessor,
622 + info=MiDashengLMProcessingInfo,
623 + dummy_inputs=MiDashengLMDummyInputsBuilder,
624 +)
625 +class MiDashengLMModel(nn.Module, SupportsMultiModal, SupportsPP):
626 +
627 + @classmethod
628 + def get_placeholder_str(cls, modality: str, i: int) -> Optional[str]:
629 + if modality.startswith("audio"):
630 + return "<|audio_bos|><|AUDIO|><|audio_eos|>"
631 +
632 + raise ValueError("Only audio modality is supported")
633 +
634 + def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
635 + super().__init__()
636 + config = vllm_config.model_config.hf_config
637 + quant_config = vllm_config.quant_config
638 + self.config = config
639 +
640 + # Initialize audio components
641 + self.audio_encoder = DashengAudioTransformer(
642 + config.audio_encoder_config,
643 + quant_config=quant_config,
644 + prefix=maybe_prefix(prefix, "audio_encoder"),
645 + )
646 + self.audio_projector = AudioProjectorSubsample(
647 + in_dim=config.audio_encoder_config.embed_dim,
648 + out_dim=config.text_config.hidden_size,
649 + downsample_rate=config.subsample_factor,
650 + quant_config=quant_config,
651 + prefix=maybe_prefix(prefix, "audio_projector"),
652 + )
653 +
654 + # Initialize language model (decoder)
655 + self.decoder = init_vllm_registered_model(
656 + vllm_config=vllm_config,
657 + hf_config=config.text_config,
658 + prefix=maybe_prefix(prefix, "decoder"),
659 + architectures=["Qwen2ForCausalLM"],
660 + )
661 +
662 + self.quant_config = quant_config
663 + self.make_empty_intermediate_tensors = (
664 + self.decoder.make_empty_intermediate_tensors)
665 +
666 + def _validate_and_reshape_mm_tensor(self, mm_input: object,
667 + name: str) -> torch.Tensor:
668 + if not isinstance(mm_input, (torch.Tensor, list)):
669 + raise ValueError(f"Incorrect type of {name}. "
670 + f"Got type: {type(mm_input)}")
671 + if isinstance(mm_input, torch.Tensor):
672 + return torch.concat(list(mm_input))
673 + else:
674 + return torch.concat(mm_input)
675 +
676 + def _parse_and_validate_audio_input(
677 + self, **kwargs: object) -> Optional[MiDashengLMAudioInputs]:
678 + input_values = kwargs.pop("input_values", None)
679 + audio_length = kwargs.pop("audio_length", None)
680 +
681 + if input_values is None:
682 + return None
683 + input_values = self._validate_and_reshape_mm_tensor(
684 + input_values, "input_values")
685 + audio_length = self._validate_and_reshape_mm_tensor(
686 + audio_length, "audio_length")
687 + if not isinstance(input_values, (torch.Tensor, list)):
688 + raise ValueError("Incorrect type of audio input features. "
689 + f"Got type: {type(input_values)}")
690 +
691 + return MiDashengLMAudioInputs(
692 + input_values=input_values,
693 + audio_length=audio_length,
694 + )
695 +
696 + def _process_audio_input(
697 + self, audio_input: MiDashengLMAudioInputs) -> torch.Tensor:
698 + # Process audio through encoder and projector
699 + input_values = audio_input["input_values"]
700 + audio_length = audio_input["audio_length"]
701 +
702 + encoder_out, encoder_atts = self.audio_encoder(input_values,
703 + audio_length)
704 + audio_embeddings, _ = self.audio_projector(encoder_out, encoder_atts)
705 + audio_embeddings = audio_embeddings.to(
706 + audio_input["input_values"].dtype)
707 + batch_size, max_audio_tokens, embed_dim = audio_embeddings.shape
708 +
709 + audio_length_np = audio_length.cpu().numpy() if isinstance(
710 + audio_length, torch.Tensor) else audio_length
711 + audio_output_lengths = [
712 + max(1, calculate_mel_frames_dasheng(
713 + int(length))) # at least one frame
714 + for length in audio_length_np
715 + ]
716 + audio_output_lengths = torch.tensor(audio_output_lengths).to(
717 + audio_embeddings.device)
718 +
719 + audio_feature_mask = (torch.arange(
720 + max_audio_tokens,
721 + device=audio_embeddings.device).unsqueeze(0).expand(
722 + batch_size, max_audio_tokens)
723 + < audio_output_lengths.unsqueeze(1))
724 +
725 + masked_audio_features = audio_embeddings[audio_feature_mask].view(
726 + -1, embed_dim)
727 +
728 + return torch.split(masked_audio_features,
729 + audio_output_lengths.tolist())
730 +
731 + def get_language_model(self) -> torch.nn.Module:
732 + return self.decoder
733 +
734 + def get_multimodal_embeddings(self,
735 + **kwargs: object) -> MultiModalEmbeddings:
736 + audio_input = self._parse_and_validate_audio_input(**kwargs)
737 +
738 + if audio_input is None:
739 + return []
740 + return self._process_audio_input(audio_input)
741 +
742 + def get_input_embeddings(
743 + self,
744 + input_ids: torch.Tensor,
745 + multimodal_embeddings: Optional[MultiModalEmbeddings] = None,
746 + ) -> torch.Tensor:
747 + inputs_embeds = self.decoder.get_input_embeddings(input_ids)
748 + if multimodal_embeddings and len(multimodal_embeddings) > 0:
749 + inputs_embeds = merge_multimodal_embeddings(
750 + input_ids,
751 + inputs_embeds,
752 + multimodal_embeddings,
753 + self.config.audio_token_id,
754 + )
755 + return inputs_embeds
756 +
757 + def forward(
758 + self,
759 + input_ids: torch.Tensor,
760 + positions: torch.Tensor,
761 + intermediate_tensors: Optional[IntermediateTensors] = None,
762 + inputs_embeds: Optional[torch.Tensor] = None,
763 + **kwargs: object,
764 + ) -> Union[torch.Tensor, IntermediateTensors]:
765 + if intermediate_tensors is not None:
766 + inputs_embeds = None
767 + elif inputs_embeds is None:
768 + multimodal_embeddings = self.get_multimodal_embeddings(**kwargs)
769 + inputs_embeds = self.get_input_embeddings(input_ids,
770 + multimodal_embeddings)
771 + input_ids = None
772 +
773 + return self.decoder.model(input_ids,
774 + positions,
775 + intermediate_tensors,
776 + inputs_embeds=inputs_embeds)
777 +
778 + def compute_logits(
779 + self,
780 + hidden_states: torch.Tensor,
781 + sampling_metadata: SamplingMetadata,
782 + ) -> Optional[torch.Tensor]:
783 + return self.decoder.compute_logits(hidden_states, sampling_metadata)
784 +
785 + def load_weights(self, weights: Iterable[tuple[str,
786 + torch.Tensor]]) -> set[str]:
787 + loader = AutoWeightsLoader(self)
788 + return loader.load_weights(weights)
vllm/model_executor/models/registry.py
+1
@@ -236,6 +236,7 @@ _MULTIMODAL_MODELS = {
236 "LlavaNextVideoForConditionalGeneration": ("llava_next_video", "LlavaNextVideoForConditionalGeneration"), # noqa: E501
237 "LlavaOnevisionForConditionalGeneration": ("llava_onevision", "LlavaOnevisionForConditionalGeneration"), # noqa: E501
238 "MantisForConditionalGeneration": ("llava", "MantisForConditionalGeneration"), # noqa: E501
239 + "MiDashengLMModel": ("midashenglm", "MiDashengLMModel"),
240 "MiniMaxVL01ForConditionalGeneration": ("minimax_vl_01", "MiniMaxVL01ForConditionalGeneration"), # noqa: E501
241 "MiniCPMO": ("minicpmo", "MiniCPMO"),
242 "MiniCPMV": ("minicpmv", "MiniCPMV"),
vllm/transformers_utils/config.py
+1
@@ -71,6 +71,7 @@ _CONFIG_REGISTRY: dict[str, type[PretrainedConfig]] = LazyConfigDict(
71 jais="JAISConfig",
72 mlp_speculator="MLPSpeculatorConfig",
73 medusa="MedusaConfig",
74 + midashenglm="MiDashengLMConfig",
75 eagle="EAGLEConfig",
76 speculators="SpeculatorsConfig",
77 nemotron="NemotronConfig",
vllm/transformers_utils/configs/__init__.py
+2
@@ -17,6 +17,7 @@ from vllm.transformers_utils.configs.falcon import RWConfig
17 from vllm.transformers_utils.configs.jais import JAISConfig
18 from vllm.transformers_utils.configs.kimi_vl import KimiVLConfig
19 from vllm.transformers_utils.configs.medusa import MedusaConfig
20 +from vllm.transformers_utils.configs.midashenglm import MiDashengLMConfig
21 from vllm.transformers_utils.configs.mlp_speculator import MLPSpeculatorConfig
22 from vllm.transformers_utils.configs.moonvit import MoonViTConfig
23 from vllm.transformers_utils.configs.nemotron import NemotronConfig
@@ -36,6 +37,7 @@ __all__ = [
37 "RWConfig",
38 "JAISConfig",
39 "MedusaConfig",
40 + "MiDashengLMConfig",
41 "MLPSpeculatorConfig",
42 "MoonViTConfig",
43 "KimiVLConfig",
vllm/transformers_utils/configs/midashenglm.py new
+101
@@ -0,0 +1,101 @@
1 +# SPDX-License-Identifier: Apache-2.0
2 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 +# Copyright 2025 Horizon team, Xiaomi MiLM Plus.
4 +# Copyright 2024 The Qwen team.
5 +# Copyright 2023 The vLLM team.
6 +# Copyright 2022 EleutherAI and the HuggingFace Inc. team. All rights reserved.
7 +#
8 +# This code is based on EleutherAI's GPT-NeoX library and the GPT-NeoX
9 +# and OPT implementations in this library. It has been modified from its
10 +# original forms to accommodate minor architectural differences compared
11 +# to GPT-NeoX and OPT used by the Meta AI team that trained the model.
12 +#
13 +# Licensed under the Apache License, Version 2.0 (the "License");
14 +# you may not use this file except in compliance with the License.
15 +# You may obtain a copy of the License at
16 +#
17 +# http://www.apache.org/licenses/LICENSE-2.0
18 +#
19 +# Unless required by applicable law or agreed to in writing, software
20 +# distributed under the License is distributed on an "AS IS" BASIS,
21 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22 +# See the License for the specific language governing permissions and
23 +# limitations under the License.
24 +from typing import Optional, Union
25 +
26 +from transformers import PretrainedConfig
27 +from transformers.models.qwen2_5_omni.configuration_qwen2_5_omni import (
28 + Qwen2_5OmniTextConfig)
29 +
30 +
31 +class DashengConfig(PretrainedConfig):
32 + model_type = "midashenglm_dasheng_encoder"
33 +
34 + def __init__(
35 + self,
36 + embed_dim: int = 768,
37 + outputdim: int = 527,
38 + patch_size: Union[int, tuple[int, int]] = 16,
39 + patch_stride: Union[int, tuple[int, int]] = 16,
40 + input_channels: int = 1,
41 + target_length: int = 1012,
42 + depth: int = 12,
43 + num_heads: int = 12,
44 + mlp_ratio: float = 4.0,
45 + qkv_bias: bool = True,
46 + init_values: Optional[float] = None,
47 + drop_rate: float = 0.0,
48 + attn_drop_rate: float = 0.0,
49 + f_min: float = 0.0,
50 + f_max: float = 8000.0,
51 + center: bool = True,
52 + win_length: int = 512,
53 + hop_length: int = 160,
54 + sample_rate: int = 16000,
55 + n_fft: int = 512,
56 + n_mels: int = 64,
57 + **kwargs,
58 + ):
59 + self.embed_dim = embed_dim
60 + self.outputdim = outputdim
61 + self.patch_size = patch_size
62 + self.patch_stride = patch_stride
63 + self.input_channels = input_channels
64 + self.target_length = target_length
65 + self.depth = depth
66 + self.num_heads = num_heads
67 + self.mlp_ratio = mlp_ratio
68 + self.qkv_bias = qkv_bias
69 + self.init_values = init_values
70 + self.drop_rate = drop_rate
71 + self.attn_drop_rate = attn_drop_rate
72 + self.f_min = f_min
73 + self.f_max = f_max
74 + self.center = center
75 + self.win_length = win_length
76 + self.hop_length = hop_length
77 + self.sample_rate = sample_rate
78 + self.n_fft = n_fft
79 + self.n_mels = n_mels
80 + super().__init__(**kwargs)
81 +
82 +
83 +class MiDashengLMConfig(PretrainedConfig):
84 + model_type = "midashenglm"
85 +
86 + def __init__(
87 + self,
88 + audio_encoder_config: Optional[dict] = None,
89 + subsample_factor: int = 5,
90 + text_config: Optional[dict] = None,
91 + audio_token_id: Optional[int] = None,
92 + **kwargs,
93 + ):
94 + self.audio_encoder_config = DashengConfig(
95 + **(audio_encoder_config or {}))
96 + self.subsample_factor = subsample_factor
97 + self.text_config = (Qwen2_5OmniTextConfig(
98 + **text_config) if text_config else Qwen2_5OmniTextConfig())
99 + self.text_config.rope_scaling = None # uses_mrope is false
100 + self.audio_token_id = audio_token_id
101 + super().__init__(**kwargs)