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)