Files
wehub-resource-sync 7ce4c8e27e
pre-commit / pre-run-check (push) Has been cancelled
pre-commit / pre-commit (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:55:37 +08:00

647 lines
22 KiB
Python

# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
from types import SimpleNamespace
import numpy as np
import pytest
import torch
from transformers import Qwen3Config
from vllm.model_executor.models.interfaces import SupportsLoRA, supports_lora
from vllm.model_executor.models.moss_audio import (
MOSS_AUDIO_BOS_TOKEN,
MOSS_AUDIO_BOS_TOKEN_ID,
MOSS_AUDIO_EOS_TOKEN,
MOSS_AUDIO_EOS_TOKEN_ID,
MOSS_AUDIO_PLACEHOLDER,
MOSS_AUDIO_TOKEN,
MOSS_AUDIO_TOKEN_ID,
MossAudioConfig,
MossAudioDummyInputsBuilder,
MossAudioEncoder,
MossAudioEncoderConfig,
MossAudioModel,
MossAudioMultiModalProcessor,
MossAudioProcessingInfo,
MossAudioProcessor,
MossQwen3ForCausalLM,
MossQwen3Model,
)
from vllm.model_executor.models.utils import AutoWeightsLoader
from vllm.multimodal.cache import MultiModalProcessorOnlyCache
from vllm.multimodal.inputs import batched_tensors_equal
from vllm.sequence import IntermediateTensors
class _Tokenizer:
def encode(self, text, add_special_tokens=False):
del add_special_tokens
return [ord(char) for char in text]
def decode(self, token_ids, **kwargs):
del kwargs
return "".join(chr(token_id) for token_id in token_ids)
def batch_decode(self, batch_token_ids, **kwargs):
return [self.decode(token_ids, **kwargs) for token_ids in batch_token_ids]
class _MMConfig:
enable_mm_embeds = False
mm_processor_cache_gb = 1
def merge_mm_processor_kwargs(self, kwargs):
return dict(kwargs)
def get_limit_per_prompt(self, modality):
del modality
return 3
class _ModelConfig:
def __init__(self):
self.model = "OpenMOSS-Team/MOSS-Audio-4B-Instruct"
self.revision = None
self.max_model_len = 4096
self.encoder_config = {}
self.dtype = torch.float32
self.hf_config = MossAudioConfig(language_config=Qwen3Config())
self.multimodal_config = _MMConfig()
def get_multimodal_config(self):
return self.multimodal_config
def get_inputs_embeds_size(self):
return None
class _ProcessingContext:
def __init__(self):
self.model_config = _ModelConfig()
self.tokenizer = _Tokenizer()
def get_tokenizer(self):
return self.tokenizer
def get_hf_config(self):
return self.model_config.hf_config
def get_mm_config(self):
return self.model_config.get_multimodal_config()
def get_merged_mm_kwargs(self, kwargs):
return self.get_mm_config().merge_mm_processor_kwargs(kwargs)
def call_hf_processor(self, hf_processor, data, kwargs):
merged_kwargs = self.get_merged_mm_kwargs(kwargs)
merged_kwargs.setdefault("return_tensors", "pt")
return hf_processor(**data, **merged_kwargs)
class _TestMossAudioProcessingInfo(MossAudioProcessingInfo):
def _get_processor_config_defaults(self):
return {}
def _vllm_config(tensor_parallel_size=1, pipeline_parallel_size=1, hf_config=None):
if hf_config is None:
hf_config = MossAudioConfig(language_config=Qwen3Config())
return SimpleNamespace(
model_config=SimpleNamespace(
hf_config=hf_config,
multimodal_config=None,
),
quant_config=None,
parallel_config=SimpleNamespace(
tensor_parallel_size=tensor_parallel_size,
pipeline_parallel_size=pipeline_parallel_size,
),
)
class _FakeAudioEncoder:
dtype = torch.float32
def __init__(self, deepstack_layers=0):
self.deepstack_layers = deepstack_layers
self.output_deepstack_hidden_states = None
self.input_shape = None
self.feature_lens = None
def __call__(self, audio_data, *, feature_lens, output_deepstack_hidden_states):
self.input_shape = tuple(audio_data.shape)
self.feature_lens = feature_lens.detach().cpu().clone()
self.output_deepstack_hidden_states = output_deepstack_hidden_states
lengths = MossAudioEncoder._compute_downsampled_length(feature_lens)
hidden_states = torch.ones(1, int(lengths.sum().item()), 8)
if not output_deepstack_hidden_states:
return hidden_states, None
return hidden_states, [
hidden_states * scale for scale in range(2, 2 + self.deepstack_layers)
]
def _patch_tensor_parallel_for_linear_layers(monkeypatch, tp_size=1, tp_rank=0):
import vllm.model_executor.layers.linear as linear_layers
import vllm.model_executor.models.moss_audio as moss_audio_module
import vllm.model_executor.parameter as parameter_module
for module in (moss_audio_module, linear_layers, parameter_module):
monkeypatch.setattr(
module, "get_tensor_model_parallel_world_size", lambda: tp_size
)
monkeypatch.setattr(
linear_layers, "get_tensor_model_parallel_rank", lambda: tp_rank
)
monkeypatch.setattr(
parameter_module, "get_tensor_model_parallel_rank", lambda: tp_rank
)
monkeypatch.setattr(
linear_layers, "tensor_model_parallel_all_reduce", lambda tensor: tensor
)
def _build_moss_audio_processor(cache=None):
ctx = _ProcessingContext()
info = _TestMossAudioProcessingInfo(ctx)
return (
MossAudioMultiModalProcessor(
info,
MossAudioDummyInputsBuilder(info),
cache=cache,
),
ctx,
)
def _assert_mm_inputs_equal(left, right):
assert left["prompt_token_ids"] == right["prompt_token_ids"]
assert left["mm_hashes"] == right["mm_hashes"]
left_placeholder = left["mm_placeholders"]["audio"][0]
right_placeholder = right["mm_placeholders"]["audio"][0]
assert left_placeholder.offset == right_placeholder.offset
assert left_placeholder.length == right_placeholder.length
assert left_placeholder.is_embed.tolist() == right_placeholder.is_embed.tolist()
assert batched_tensors_equal(
left["mm_kwargs"].get_data(),
right["mm_kwargs"].get_data(),
)
@pytest.mark.parametrize(
("prompt", "prefix"),
[
(
f"before {MOSS_AUDIO_PLACEHOLDER} after",
[*[ord(char) for char in "before "], MOSS_AUDIO_BOS_TOKEN_ID],
),
(
f"before {MOSS_AUDIO_BOS_TOKEN}{MOSS_AUDIO_TOKEN}"
f"{MOSS_AUDIO_TOKEN}{MOSS_AUDIO_EOS_TOKEN} after",
[*[ord(char) for char in "before "], MOSS_AUDIO_BOS_TOKEN_ID],
),
("Describe this audio.", [MOSS_AUDIO_BOS_TOKEN_ID]),
],
)
def test_moss_audio_processor_expands_audio_placeholders(prompt, prefix):
raw_mel_len = 17
processed = MossAudioProcessor(_Tokenizer())(
text=prompt, audio=[torch.zeros(160 * raw_mel_len)]
)
input_ids = processed["input_ids"][0].tolist()
assert input_ids[: len(prefix)] == prefix
assert input_ids.count(MOSS_AUDIO_BOS_TOKEN_ID) == 1
assert input_ids.count(MOSS_AUDIO_EOS_TOKEN_ID) == 1
assert input_ids.count(MOSS_AUDIO_TOKEN_ID) == (
MossAudioEncoder.compute_num_audio_tokens(raw_mel_len)
)
assert processed["audio_data"].shape == (1, 128, raw_mel_len)
assert processed["audio_data_seqlens"].tolist() == [raw_mel_len]
def test_moss_audio_processor_preserves_placeholder_without_audio():
processed = MossAudioProcessor(_Tokenizer())(
text=f"before {MOSS_AUDIO_PLACEHOLDER} after"
)
assert processed["input_ids"][0].tolist() == [
*[ord(char) for char in "before "],
MOSS_AUDIO_BOS_TOKEN_ID,
MOSS_AUDIO_TOKEN_ID,
MOSS_AUDIO_EOS_TOKEN_ID,
*[ord(char) for char in " after"],
]
assert "audio_data" not in processed
assert "audio_data_seqlens" not in processed
def test_moss_audio_multimodal_processor_handles_token_and_cache_paths():
raw_mel_len = 17
audio = np.zeros(160 * raw_mel_len, dtype=np.float32)
prompt = f"{MOSS_AUDIO_PLACEHOLDER}\nTranscribe this audio."
baseline_processor, ctx = _build_moss_audio_processor()
mm_items = baseline_processor.info.parse_mm_data({"audio": [audio]})
token_prompt = ctx.get_tokenizer().encode(prompt, add_special_tokens=False)
baseline_text = baseline_processor(
prompt,
mm_items=mm_items,
hf_processor_mm_kwargs={},
)
baseline_token = baseline_processor(
token_prompt,
mm_items=mm_items,
hf_processor_mm_kwargs={},
)
cache = MultiModalProcessorOnlyCache(ctx.model_config)
cached_processor, _ = _build_moss_audio_processor(cache=cache)
cached_text_miss = cached_processor(
prompt,
mm_items=mm_items,
hf_processor_mm_kwargs={},
)
cached_text_hit = cached_processor(
prompt,
mm_items=mm_items,
hf_processor_mm_kwargs={},
)
cached_token_hit = cached_processor(
token_prompt,
mm_items=mm_items,
hf_processor_mm_kwargs={},
)
expected_audio_tokens = MossAudioEncoder.compute_num_audio_tokens(raw_mel_len)
prompt_token_ids = baseline_text["prompt_token_ids"]
assert prompt_token_ids.count(MOSS_AUDIO_TOKEN_ID) == expected_audio_tokens
assert baseline_text["mm_placeholders"]["audio"][0].length == (
expected_audio_tokens + 2
)
_assert_mm_inputs_equal(baseline_text, baseline_token)
_assert_mm_inputs_equal(baseline_text, cached_text_miss)
_assert_mm_inputs_equal(baseline_text, cached_text_hit)
_assert_mm_inputs_equal(baseline_text, cached_token_hit)
def test_moss_audio_supports_language_model_lora_only():
assert supports_lora(MossAudioModel)
model = object.__new__(MossAudioModel)
assert isinstance(model, SupportsLoRA)
mapping = model.get_mm_mapping()
assert mapping.language_model == ["language_model."]
assert mapping.tower_model == []
assert mapping.connector == []
def test_moss_audio_error_paths():
model = object.__new__(MossAudioModel)
with pytest.raises(ValueError, match="DeepStack audio token count mismatch"):
model._cache_deepstack_input_embeds(
inputs_embeds=torch.zeros(4, 8),
deepstack_embeddings=((torch.ones(1, 8),),),
is_multimodal=torch.tensor([False, True, True, False]),
)
with pytest.raises(ValueError, match="too short"):
MossAudioProcessor(_Tokenizer())(
text=MOSS_AUDIO_PLACEHOLDER, audio=[torch.empty(0)]
)
with pytest.raises(ValueError, match="too short"):
model._parse_and_validate_audio_input(
audio_data=torch.zeros(1, 128, 1),
audio_data_seqlens=torch.tensor([0], dtype=torch.long),
)
def test_moss_audio_validates_tp_config():
vllm_config = _vllm_config(tensor_parallel_size=2)
vllm_config.model_config.hf_config.adapter_hidden_size = 7
with pytest.raises(ValueError, match="adapter_hidden_size"):
MossAudioModel(vllm_config=vllm_config)
vllm_config = _vllm_config(tensor_parallel_size=2)
vllm_config.model_config.hf_config.audio_config.d_model = 6
vllm_config.model_config.hf_config.audio_config.encoder_attention_heads = 3
with pytest.raises(ValueError, match="encoder_attention_heads"):
MossAudioModel(vllm_config=vllm_config)
def test_moss_audio_rejects_audio_data_list_seqlen_count_mismatch():
model = object.__new__(MossAudioModel)
with pytest.raises(ValueError, match="audio_data batch size"):
model._parse_and_validate_audio_input(
audio_data=[torch.zeros(128, 8), torch.zeros(128, 11)],
audio_data_seqlens=torch.tensor([8], dtype=torch.long),
)
@pytest.mark.parametrize("deepstack_scales", [(), (7, 11)])
def test_moss_audio_embed_multimodal_packs_by_audio(deepstack_scales):
model = object.__new__(MossAudioModel)
model.audio_encoder = _FakeAudioEncoder(len(deepstack_scales))
model.audio_adapter = lambda hidden_states: hidden_states * 5
model.deepstack_audio_merger_list = [
lambda hidden_states, scale=scale: hidden_states * scale
for scale in deepstack_scales
]
model.deepstack_input_embeds = None
embeddings = model.embed_multimodal(
audio_data=torch.zeros(2, 128, 9),
audio_data_seqlens=torch.tensor([8, 9], dtype=torch.long),
)
assert model.audio_encoder.output_deepstack_hidden_states is bool(deepstack_scales)
assert [embeds.shape for embeds in embeddings] == [
torch.Size([1, 8 * (1 + len(deepstack_scales))]),
torch.Size([2, 8 * (1 + len(deepstack_scales))]),
]
if not deepstack_scales:
assert model.deepstack_input_embeds is None
return
main_embeddings, deepstack_embeddings = model._split_multimodal_embeddings(
embeddings, hidden_size=8
)
assert [embeds.shape for embeds in main_embeddings] == [
torch.Size([1, 8]),
torch.Size([2, 8]),
]
assert [[e.shape for e in layer] for layer in deepstack_embeddings] == [
[torch.Size([1, 8]), torch.Size([2, 8])] for _ in deepstack_scales
]
assert torch.equal(main_embeddings[0], torch.full((1, 8), 5.0))
for idx, scale in enumerate(deepstack_scales):
assert torch.equal(
deepstack_embeddings[idx][0],
torch.full((1, 8), float((idx + 2) * scale)),
)
def test_moss_audio_embed_input_ids_caches_packed_deepstack():
class _FakeLanguageModel:
def embed_input_ids(self, input_ids):
return torch.zeros(input_ids.shape[0], 8)
model = object.__new__(MossAudioModel)
model.language_model = _FakeLanguageModel()
model.deepstack_audio_merger_list = [object(), object()]
model.deepstack_input_embeds = None
multimodal_embeddings = (
torch.cat([torch.full((1, 8), x) for x in (5.0, 14.0, 33.0)], dim=-1),
torch.cat([torch.full((2, 8), x) for x in (7.0, 22.0, 44.0)], dim=-1),
)
is_multimodal = torch.tensor([False, True, True, True, False])
inputs_embeds = model.embed_input_ids(
input_ids=torch.arange(5),
multimodal_embeddings=multimodal_embeddings,
is_multimodal=is_multimodal,
)
assert torch.equal(inputs_embeds[1], torch.full((8,), 5.0))
assert torch.equal(inputs_embeds[2], torch.full((8,), 7.0))
assert torch.equal(inputs_embeds[3], torch.full((8,), 7.0))
assert model.deepstack_input_embeds is not None
tensors = model.deepstack_input_embeds.tensors
assert set(tensors) == {"deepstack_input_embeds_0", "deepstack_input_embeds_1"}
for tensor in tensors.values():
assert tensor[is_multimodal].abs().sum() > 0
assert torch.equal(tensor[~is_multimodal], torch.zeros(2, 8))
def _patch_pp_group(monkeypatch, *, first=True, last=True):
import vllm.model_executor.models.moss_audio as moss_audio_module
monkeypatch.setattr(
moss_audio_module,
"get_pp_group",
lambda: SimpleNamespace(is_first_rank=first, is_last_rank=last),
)
def test_moss_audio_pp_forward_routes_deepstack(monkeypatch):
for first in (True, False):
calls: list[dict[str, object]] = []
def fake_lm(*args, _calls=calls, **kwargs):
del args
_calls.append(kwargs)
return torch.ones(1, 1)
_patch_pp_group(monkeypatch, first=first)
model = object.__new__(MossAudioModel)
torch.nn.Module.__init__(model)
model.language_model = fake_lm
cached = IntermediateTensors({"deepstack_input_embeds_0": torch.ones(3, 8)})
inter = IntermediateTensors(
{
"hidden_states": torch.ones(3, 8),
"residual": torch.zeros(3, 8),
"deepstack_input_embeds_0": torch.full((3, 8), 5.0),
}
)
inputs_embeds = torch.full((3, 8), 9.0)
model.deepstack_input_embeds = cached
model.forward(
input_ids=None,
positions=torch.arange(3),
intermediate_tensors=None if first else inter,
inputs_embeds=inputs_embeds if first else None,
)
kwargs = calls[0]
assert kwargs["inputs_embeds"] is (inputs_embeds if first else None)
assert kwargs["deepstack_input_embeds"] is (cached if first else inter)
assert model.deepstack_input_embeds is None
calls = []
def fake_lm_non_first_rank(*args, **kwargs):
del args
calls.append(kwargs)
return torch.ones(1, 1)
_patch_pp_group(monkeypatch, first=False)
model = object.__new__(MossAudioModel)
torch.nn.Module.__init__(model)
model.language_model = fake_lm_non_first_rank
model.deepstack_input_embeds = IntermediateTensors({})
inter = IntermediateTensors(
{
"hidden_states": torch.ones(3, 8),
"residual": torch.zeros(3, 8),
}
)
model.forward(
input_ids=None,
positions=torch.arange(3),
intermediate_tensors=inter,
inputs_embeds=torch.ones(3, 8),
)
assert calls[0]["inputs_embeds"] is None
assert calls[0]["deepstack_input_embeds"] is inter
def test_moss_qwen3_deepstack_keys_for_pp(monkeypatch):
class AddOne(torch.nn.Module):
def forward(self, positions, hidden_states, residual):
del positions, residual
return hidden_states + 1, torch.zeros_like(hidden_states)
def make_model(num_layers, deepstack_layers=None):
model = object.__new__(MossQwen3Model)
torch.nn.Module.__init__(model)
model.start_layer, model.end_layer = 0, num_layers
model.layers = torch.nn.ModuleList([AddOne() for _ in range(num_layers)])
model.norm = lambda hidden_states, residual: (hidden_states, residual)
model._maybe_add_hidden_state = lambda aux, *args: aux
model.deepstack_inject_layer_indices = (
range(0) if deepstack_layers is None else deepstack_layers
)
return model
_patch_pp_group(monkeypatch, first=True, last=True)
output = make_model(3).forward(
input_ids=None,
positions=torch.arange(2),
inputs_embeds=torch.zeros(2, 4),
deepstack_input_embeds=IntermediateTensors(
{
"deepstack_input_embeds_2": torch.full((2, 4), 5.0),
}
),
)
assert torch.equal(output, torch.full((2, 4), 8.0))
_patch_pp_group(monkeypatch, first=True, last=False)
deepstack = IntermediateTensors(
{
"deepstack_input_embeds_0": torch.full((2, 4), 7.0),
"deepstack_input_embeds_3": torch.full((2, 4), 11.0),
}
)
output = make_model(2, range(4)).forward(
input_ids=None,
positions=torch.arange(2),
inputs_embeds=torch.zeros(2, 4),
deepstack_input_embeds=deepstack,
)
assert isinstance(output, IntermediateTensors)
assert set(output.tensors) == {
"hidden_states",
"residual",
"deepstack_input_embeds_2",
"deepstack_input_embeds_3",
}
assert torch.equal(output["hidden_states"], torch.full((2, 4), 9.0))
assert torch.equal(output["deepstack_input_embeds_2"], torch.zeros(2, 4))
assert output["deepstack_input_embeds_3"] is deepstack["deepstack_input_embeds_3"]
inner_model = make_model(0, range(2))
inner_model.make_empty_intermediate_tensors = lambda batch, dtype, device: (
IntermediateTensors(
{
"hidden_states": torch.zeros(batch, 4, dtype=dtype, device=device),
"residual": torch.zeros(batch, 4, dtype=dtype, device=device),
}
)
)
language_model = object.__new__(MossQwen3ForCausalLM)
torch.nn.Module.__init__(language_model)
language_model.model = inner_model
language_model.config = SimpleNamespace(hidden_size=4)
language_model.deepstack_inject_layer_indices = range(2)
tensors = MossQwen3ForCausalLM.make_empty_intermediate_tensors(
language_model,
batch_size=3,
dtype=torch.float16,
device=torch.device("cpu"),
)
assert set(tensors.tensors) == {
"hidden_states",
"residual",
"deepstack_input_embeds_0",
"deepstack_input_embeds_1",
}
assert tensors["deepstack_input_embeds_0"].shape == (3, 4)
assert tensors["deepstack_input_embeds_0"].dtype == torch.float16
_patch_pp_group(monkeypatch, first=True, last=False)
forward_tensors = inner_model.forward(
input_ids=None,
positions=torch.arange(3),
inputs_embeds=torch.ones(3, 4, dtype=torch.float16),
deepstack_input_embeds=None,
)
assert isinstance(forward_tensors, IntermediateTensors)
assert set(forward_tensors.tensors) == set(tensors.tensors)
def test_moss_audio_encoder_loads_realistic_attention_weight_names(monkeypatch):
from vllm.config import VllmConfig, set_current_vllm_config
from vllm.config.device import DeviceConfig
_patch_tensor_parallel_for_linear_layers(monkeypatch, tp_size=2)
config = MossAudioEncoderConfig(
d_model=8,
output_dim=8,
num_mel_bins=8,
encoder_layers=1,
encoder_attention_heads=2,
encoder_ffn_dim=16,
downsample_hidden_size=2,
deepstack_encoder_layer_indexes=[],
)
with set_current_vllm_config(VllmConfig(device_config=DeviceConfig(device="cpu"))):
encoder = MossAudioEncoder(config)
attention = encoder.layers[0].self_attn
assert all(hasattr(attention, name) for name in ("q_proj", "k_proj", "v_proj"))
assert hasattr(attention, "out_proj")
assert not hasattr(attention, "qkv")
assert attention.k_proj.bias is None
weight_names = [
"layers.0.self_attn.q_proj.weight",
"layers.0.self_attn.q_proj.bias",
"layers.0.self_attn.k_proj.weight",
"layers.0.self_attn.v_proj.weight",
"layers.0.self_attn.v_proj.bias",
"layers.0.self_attn.out_proj.weight",
"layers.0.self_attn.out_proj.bias",
"conv1.weight",
"conv1.bias",
]
params = dict(encoder.named_parameters(remove_duplicate=False))
assert "layers.0.self_attn.k_proj.bias" not in params
weights = {
name: torch.full_like(params[name], fill_value=float(i + 1))
for i, name in enumerate(weight_names)
}
loaded = AutoWeightsLoader(encoder).load_weights(weights.items())
assert "load_weights" not in MossAudioEncoder.__dict__
assert loaded == set(weight_names)
assert not any(".qkv." in name for name in loaded)
assert torch.equal(
params["layers.0.self_attn.q_proj.weight"],
weights["layers.0.self_attn.q_proj.weight"],
)