import collections import collections.abc import logging from collections.abc import Callable, Sequence from typing import Iterable, List, Optional, Tuple, TypeAlias, cast import torch import torch.nn as nn import torchaudio.functional as F from transformers import PretrainedConfig from sglang.srt.layers.attention.vision import VisionAttention from sglang.srt.layers.conv import Conv2dLayer from sglang.srt.layers.linear import ColumnParallelLinear, RowParallelLinear from sglang.srt.layers.quantization.base_config import QuantizationConfig from sglang.srt.managers.mm_utils import ( MultiModalityDataPaddingPatternMultimodalTokens, general_mm_embed_routine, ) from sglang.srt.managers.schedule_batch import ( Modality, MultimodalDataItem, MultimodalInputs, ) from sglang.srt.model_executor.forward_batch_info import ForwardBatch from sglang.srt.model_loader.weight_utils import default_weight_loader from sglang.srt.models.qwen2 import Qwen2ForCausalLM from sglang.srt.utils import add_prefix logger = logging.getLogger(__name__) _Tuple2: TypeAlias = int | tuple[int, int] | Sequence[int] def _resolve_tuple2(x: _Tuple2) -> tuple[int, int]: if isinstance(x, collections.abc.Sequence): assert ( len(x) == 2 ), f"Expected a sequence of length 2, got {x} with length {len(x)}" return cast(tuple[int, int], tuple(x)) return (x, x) def calculate_mel_frames_dasheng( audio_length_samples: int, n_fft: int = 512, hop_size: int = 160, dasheng_subsampling: int = 4, center=True, model_subsampling: int = 5, ) -> int: """Calculate the number of Mel-spectrogram frames.""" if center: audio_length_samples = audio_length_samples + n_fft return ( int(1 + ((audio_length_samples - n_fft) / hop_size)) // dasheng_subsampling // model_subsampling ) class AudioPatchEmbed(nn.Module): def __init__( self, input_size: _Tuple2 = 64, patch_size: _Tuple2 = 16, patch_stride: _Tuple2 = 16, in_chans: int = 1, embed_dim: int = 768, norm_layer: Callable | None = None, flatten: bool = False, ): super().__init__() self.input_size = _resolve_tuple2(input_size) self.patch_size = _resolve_tuple2(patch_size) self.patch_stride = _resolve_tuple2(patch_stride) self.grid_size = ( self.input_size[0] // self.patch_stride[0], self.input_size[1] // self.patch_stride[1], ) self.num_patches = self.grid_size[0] * self.grid_size[1] self.flatten = flatten self.proj = Conv2dLayer( in_chans, embed_dim, kernel_size=self.patch_size, stride=self.patch_stride, ) self.norm = norm_layer(embed_dim) if norm_layer else nn.Identity() def forward(self, x: torch.Tensor) -> torch.Tensor: x = self.proj(x) if self.flatten: x = torch.permute(torch.flatten(x, 2, 3), (0, 2, 1)) x = self.norm(x) return x class LayerScale(nn.Module): def __init__(self, dim, init_values=1e-5, inplace=False): super().__init__() self.inplace = inplace self.gamma = nn.Parameter(init_values * torch.ones(dim)) def forward(self, x: torch.Tensor) -> torch.Tensor: return x.mul_(self.gamma) if self.inplace else x * self.gamma class DashengMlp(nn.Module): def __init__( self, in_features: int, hidden_features: int | None = None, out_features: int | None = None, quant_config: Optional[QuantizationConfig] = None, prefix: str = "", ): super().__init__() out_features = out_features or in_features hidden_features = hidden_features or in_features self.fc1 = ColumnParallelLinear( input_size=in_features, output_size=hidden_features, bias=True, quant_config=quant_config, prefix=add_prefix("fc1", prefix), ) self.act = nn.GELU() self.fc2 = RowParallelLinear( input_size=hidden_features, output_size=out_features, bias=True, quant_config=quant_config, prefix=add_prefix("fc2", prefix), ) def forward(self, x: torch.Tensor) -> torch.Tensor: x, _ = self.fc1(x) x = self.act(x) x, _ = self.fc2(x) return x class DashengAttention(nn.Module): """Audio encoder attention using VisionAttention for compatibility.""" def __init__( self, dim: int, num_heads: int = 8, qkv_bias: bool = False, quant_config: Optional[QuantizationConfig] = None, prefix: str = "", ): super().__init__() assert dim % num_heads == 0, "dim should be divisible by num_heads" self.embed_dim = dim self.num_heads = num_heads self.head_dim = self.embed_dim // self.num_heads self.scale = self.head_dim**-0.5 self.attn = VisionAttention( embed_dim=dim, num_heads=num_heads, projection_size=dim, use_qkv_parallel=True, proj_bias=True, qkv_bias=qkv_bias, qkv_backend="sdpa", softmax_in_single_precision=False, flatten_batch=False, quant_config=quant_config, prefix=prefix, ) def forward(self, x: torch.Tensor, mask: torch.Tensor | None = None): """ Args: x: [B, N, C] tensor mask: [B, N] boolean mask """ attn_mask = None if mask is not None: attn_mask = mask.unsqueeze(1).unsqueeze(2) # [B, 1, 1, N] attn_mask = attn_mask.float() attn_mask = (1.0 - attn_mask) * -10000.0 x = self.attn(x, attn_mask=attn_mask) return x class DashengBlock(nn.Module): def __init__( self, dim: int, num_heads: int, mlp_ratio: float = 4.0, qkv_bias: bool = False, init_values: float | None = None, quant_config: Optional[QuantizationConfig] = None, prefix: str = "", ): super().__init__() self.norm1 = nn.LayerNorm(dim, eps=1e-6) self.attn = DashengAttention( dim, num_heads=num_heads, qkv_bias=qkv_bias, quant_config=quant_config, prefix=add_prefix("attn", prefix), ) self.ls1 = ( LayerScale(dim, init_values=init_values) if init_values else nn.Identity() ) self.norm2 = nn.LayerNorm(dim, eps=1e-6) self.mlp = DashengMlp( in_features=dim, hidden_features=int(dim * mlp_ratio), quant_config=quant_config, prefix=add_prefix("mlp", prefix), ) self.ls2 = ( LayerScale(dim, init_values=init_values) if init_values else nn.Identity() ) def forward( self, x: torch.Tensor, mask: torch.Tensor | None = None, ) -> torch.Tensor: x = x + self.ls1(self.attn(self.norm1(x), mask)) x = x + self.ls2(self.mlp(self.norm2(x))) return x class DashengFrontend(nn.Module): """Audio frontend that converts waveforms to log mel-spectrograms.""" def __init__(self, config: PretrainedConfig): super().__init__() self.n_fft = config.n_fft self.hop_length = config.hop_length self.win_length = config.win_length self.center = config.center spectrogram_window = torch.hann_window(config.win_length) self.register_buffer( "spectrogram_window", spectrogram_window, persistent=False, ) self.spectrogram_window: torch.Tensor melscale_fbanks = F.melscale_fbanks( n_freqs=config.n_fft // 2 + 1, f_min=config.f_min, f_max=config.f_max, n_mels=config.n_mels, sample_rate=config.sample_rate, ) self.register_buffer("melscale_fbanks", melscale_fbanks, persistent=False) self.melscale_fbanks: torch.Tensor def forward(self, waveform: torch.Tensor) -> torch.Tensor: """Convert waveform to log mel-spectrogram. Args: waveform: [B, T] tensor of audio samples Returns: log_mel_spectrogram: [B, n_mels, time] tensor """ spectrogram = F.spectrogram( waveform=waveform.to(torch.float32), pad=0, window=self.spectrogram_window, n_fft=self.n_fft, hop_length=self.hop_length, win_length=self.win_length, power=2, normalized=False, center=self.center, ) mel_spectrogram = (spectrogram.mT @ self.melscale_fbanks.to(torch.float32)).mT log_mel_spectrogram = F.amplitude_to_DB( mel_spectrogram.unsqueeze(1), multiplier=10, amin=1e-10, db_multiplier=0, top_db=120, ).squeeze(1) return log_mel_spectrogram.to(waveform.dtype) class DashengAudioTransformer(nn.Module): """Audio encoder transformer.""" def __init__( self, config: PretrainedConfig, quant_config: Optional[QuantizationConfig] = None, prefix: str = "", ): super().__init__() self.target_length = config.target_length self.hop_length = config.hop_length self.front_end = DashengFrontend(config) self.init_bn = nn.BatchNorm2d(config.n_mels, momentum=0.01) self.patch_embed = AudioPatchEmbed( input_size=(config.n_mels, config.target_length), embed_dim=config.embed_dim, in_chans=config.input_channels, patch_size=config.patch_size, flatten=False, patch_stride=config.patch_stride, ) self.time_pos_embed = nn.Parameter( torch.empty(1, config.embed_dim, 1, self.patch_embed.grid_size[1]) ) self.freq_pos_embed = nn.Parameter( torch.empty(1, config.embed_dim, self.patch_embed.grid_size[0], 1) ) self.blocks = nn.ModuleList( DashengBlock( dim=config.embed_dim, num_heads=config.num_heads, mlp_ratio=config.mlp_ratio, qkv_bias=config.qkv_bias, init_values=config.init_values, quant_config=quant_config, prefix=add_prefix(f"blocks.{i}", prefix), ) for i in range(config.depth) ) self.norm = nn.LayerNorm(config.embed_dim, eps=1e-6) def forward_features( self, x: torch.Tensor, mask: torch.Tensor | None = None, ) -> torch.Tensor: t = x.shape[-1] x = x + self.time_pos_embed[:, :, :, :t] x = x + self.freq_pos_embed[:, :, :, :] x = torch.permute(torch.flatten(x, 2, 3), (0, 2, 1)) for block in self.blocks: x = block(x, mask) x = self.norm(x) return x def _to_mask(self, lengths: torch.Tensor, max_length: int) -> torch.Tensor: batch_size = len(lengths) idx = torch.arange(max_length, device=lengths.device) idx = idx.repeat(batch_size).view(batch_size, max_length) mask = (idx < lengths.unsqueeze(-1)).bool() return mask def forward( self, x: torch.Tensor, x_length: torch.Tensor | None = None, ) -> tuple[torch.Tensor, torch.Tensor | None]: """ Args: x: [B, T] audio waveform tensor x_length: [B] tensor of audio lengths Returns: x: [B, seq_len, embed_dim] encoded features mask: [B, seq_len] mask tensor """ x = self.front_end(x) x = x.to(self.time_pos_embed.dtype) target_length_in_patches = self.target_length // 4 x = x.unsqueeze(1) x = torch.permute(x, (0, 2, 1, 3)) x = self.init_bn(x) x = torch.permute(x, (0, 2, 1, 3)) x = self.patch_embed(x) t = x.shape[-1] input_splits = x.split(target_length_in_patches, dim=-1) if x_length is not None: assert len(x_length) == len( x ), "batchsizes of input x and x_length need to be same" assert x_length.ndim == 1, "Lengths are of size (B,)" scaled_lengths = (x_length / (self.hop_length * 4)).long() mask = self._to_mask(max_length=t, lengths=scaled_lengths) split_masks = mask.split(target_length_in_patches, dim=-1) else: mask = None split_masks = [None] * len(input_splits) outputs = [] for split_x, split_mask in zip(input_splits, split_masks): forward_kwargs = {} forward_kwargs["mask"] = split_mask split_x = self.forward_features(split_x, **forward_kwargs) outputs.append(split_x) x = torch.cat(outputs, dim=1) return x, mask class AudioProjectorSubsample(nn.Module): """Audio projector with subsampling.""" def __init__( self, in_dim: int, out_dim: int, downsample_rate=5, dtype: torch.dtype | None = None, quant_config: Optional[QuantizationConfig] = None, prefix: str = "", ): super().__init__() self.k = downsample_rate self.fc1 = ColumnParallelLinear( input_size=in_dim * self.k, output_size=out_dim, bias=False, quant_config=quant_config, prefix=add_prefix("net.0", prefix), ) self.act = nn.GELU() self.fc2 = RowParallelLinear( input_size=out_dim, output_size=out_dim, bias=False, quant_config=quant_config, prefix=add_prefix("net.2", prefix), ) def forward(self, x, mask=None): batch_size, seq_len, dim = x.shape num_frames_to_discard = seq_len % self.k if num_frames_to_discard > 0: x = x[:, :-num_frames_to_discard, :] if mask is not None: mask = mask[:, :-num_frames_to_discard] if mask is None: mask = torch.ones(x.shape[:-1], dtype=torch.long, device=x.device) x = x.reshape(batch_size, -1, self.k * dim) x, _ = self.fc1(x) x = self.act(x) x, _ = self.fc2(x) mask = mask.reshape(batch_size, -1, self.k) mask = mask.any(dim=-1).long() return x, mask class MiDashengLMModel(nn.Module): """MiDashengLM model for audio-language processing.""" default_bitsandbytes_target_modules = [ ".fc1.", ".fc2.", ".gate_up_proj.", ".down_proj.", ".q_proj.", ".k_proj.", ".v_proj.", ".o_proj.", ] bitsandbytes_stacked_params_mapping = { "q_proj": ("qkv_proj", 0), "k_proj": ("qkv_proj", 1), "v_proj": ("qkv_proj", 2), "gate_proj": ("gate_up_proj", 0), "up_proj": ("gate_up_proj", 1), } def __init__( self, config: PretrainedConfig, quant_config: Optional[QuantizationConfig] = None, prefix: str = "", ) -> None: super().__init__() self.config = config rope_scaling = config.text_config.rope_parameters if rope_scaling: if "mrope_section" in rope_scaling: # Remove mrope_section from rope_parameters so downstream # code treats this as standard rotary embedding. del rope_scaling["mrope_section"] self.audio_encoder = DashengAudioTransformer( config.audio_encoder_config, quant_config=quant_config, prefix=add_prefix("audio_encoder", prefix), ) self.audio_projector = AudioProjectorSubsample( in_dim=config.audio_encoder_config.embed_dim, out_dim=config.text_config.hidden_size, downsample_rate=config.subsample_factor, quant_config=quant_config, prefix=add_prefix("audio_projector", prefix), ) self.language_model = Qwen2ForCausalLM( config.text_config, quant_config=quant_config, prefix=add_prefix("decoder", prefix), ) self.logits_processor = self.language_model.logits_processor self.quant_config = quant_config def pad_input_ids(self, input_ids: List[int], mm_inputs: MultimodalInputs): """Pad input IDs with multimodal tokens.""" pattern = MultiModalityDataPaddingPatternMultimodalTokens() return pattern.pad_input_tokens(input_ids, mm_inputs) def get_audio_feature(self, items: List[MultimodalDataItem]) -> torch.Tensor: """Process audio inputs and return embeddings. Args: items: List of multimodal data items containing audio features Returns: audio_embeddings: Concatenated audio embeddings """ logger.debug("=" * 80) logger.debug(f"get_audio_feature called with {len(items)} items") logger.debug("=" * 80) for i, item in enumerate(items): logger.debug(f"Item {i} feature shape: {item.feature.shape}") logger.debug( f"Item {i} audio_length: {getattr(item, 'audio_length', 'NOT SET')}" ) logger.debug(f"Item {i} pad_value: {getattr(item, 'pad_value', 'NOT SET')}") logger.debug(f"Item {i} hash: {getattr(item, 'hash', 'NOT SET')}") input_values = torch.cat([item.feature for item in items], dim=0) logger.debug(f"Concatenated input_values shape: {input_values.shape}") audio_lengths = [] for item in items: if hasattr(item, "audio_length") and item.audio_length is not None: audio_lengths.append(item.audio_length) else: audio_lengths.append(item.feature.shape[-1]) audio_length = torch.tensor(audio_lengths, device=input_values.device) logger.debug(f"audio_length: {audio_length}") encoder_out, encoder_atts = self.audio_encoder(input_values, audio_length) logger.debug(f"Encoder output shape: {encoder_out.shape}") audio_embeddings, _ = self.audio_projector(encoder_out, encoder_atts) audio_embeddings = audio_embeddings.to(input_values.dtype) logger.debug(f"Projector output shape: {audio_embeddings.shape}") batch_size, max_audio_tokens, embed_dim = audio_embeddings.shape logger.debug(f"Using all {max_audio_tokens} audio tokens from projector output") masked_audio_features = audio_embeddings.reshape(-1, embed_dim) logger.debug(f"Final output shape: {masked_audio_features.shape}") logger.debug( f"Stats: min={masked_audio_features.min().item():.4f}, max={masked_audio_features.max().item():.4f}" ) logger.debug( f"Audio embeddings dtype: {masked_audio_features.dtype}, device: {masked_audio_features.device}" ) logger.debug( f"First 5 values of first audio token: {masked_audio_features[0, :5].tolist()}" ) logger.debug("=" * 80) return masked_audio_features def get_input_embeddings(self): return self.language_model.model.embed_tokens @torch.no_grad() def forward( self, input_ids: torch.Tensor, positions: torch.Tensor, forward_batch: ForwardBatch, **kwargs, ): """Run forward pass for MiDashengLM. Args: input_ids: Flattened (concatenated) input_ids corresponding to a batch. positions: Flattened (concatenated) position ids corresponding to a batch. forward_batch: Forward batch information including multimodal data. """ if forward_batch.contains_mm_inputs(): logger.debug("=" * 80) logger.debug(f"input_ids shape: {input_ids.shape}") logger.debug(f"input_ids first 20: {input_ids[:20].tolist()}") logger.debug( f"input_ids unique values count: {len(torch.unique(input_ids))}" ) if forward_batch.mm_inputs and len(forward_batch.mm_inputs) > 0: mm_input = forward_batch.mm_inputs[0] if mm_input and len(mm_input.mm_items) > 0: pad_value = mm_input.mm_items[0].pad_value logger.debug(f"Expected pad_value: {pad_value}") logger.debug( f"Count of pad_value in input_ids: {(input_ids == pad_value).sum().item()}" ) if hasattr(mm_input, "audio_token_id") and mm_input.audio_token_id: logger.debug(f"audio_token_id: {mm_input.audio_token_id}") logger.debug( f"Count of audio_token_id in input_ids: {(input_ids == mm_input.audio_token_id).sum().item()}" ) logger.debug("=" * 80) return general_mm_embed_routine( input_ids=input_ids, forward_batch=forward_batch, language_model=self.language_model, positions=positions, data_embedding_funcs={Modality.AUDIO: self.get_audio_feature}, ) def load_weights(self, weights: Iterable[Tuple[str, torch.Tensor]]): """Load model weights.""" params_dict = dict(self.named_parameters(remove_duplicate=False)) buffers_dict = dict(self.named_buffers()) audio_encoder_loaded = [] audio_projector_loaded = [] skipped_weights = [] decoder_weights = [] for name, loaded_weight in weights: if "rotary_emb.inv_freq" in name: continue if "rotary_emb.cos_cached" in name or "rotary_emb.sin_cached" in name: continue if name.startswith("decoder"): decoder_weights.append((name, loaded_weight)) continue original_name = name if "audio_encoder.front_end" in name: if ".mel_scale.fb" in name: name = name.replace(".mel_scale.fb", ".melscale_fbanks") elif ".spectrogram.window" in name: name = name.replace(".spectrogram.window", ".spectrogram_window") if "audio_encoder" in name and ".attn.qkv." in name: name = name.replace(".attn.qkv.", ".attn.attn.qkv_proj.") if "audio_encoder" in name and ".attn.proj." in name: name = name.replace(".attn.proj.", ".attn.attn.proj.") if "audio_projector" in name: name = name.replace(".net.0.", ".fc1.") name = name.replace(".net.2.", ".fc2.") if ( name.endswith(".bias") and name not in params_dict and name not in buffers_dict ): skipped_weights.append(f"{original_name} (bias not in params/buffers)") continue if name in params_dict: param = params_dict[name] weight_loader = getattr(param, "weight_loader", default_weight_loader) weight_loader(param, loaded_weight) elif name in buffers_dict: buffers_dict[name].copy_(loaded_weight) else: if "audio_projector" in original_name: skipped_weights.append(f"{original_name} -> {name} (NOT IN MODEL)") else: skipped_weights.append(f"{original_name} (not in model)") continue if "audio_encoder" in original_name: audio_encoder_loaded.append(original_name) elif "audio_projector" in original_name: audio_projector_loaded.append(original_name) if decoder_weights: logger.debug( f"Passing {len(decoder_weights)} decoder weights to language_model.load_weights()" ) decoder_weights_stripped = [ (name.replace("decoder.", "", 1), weight) for name, weight in decoder_weights ] self.language_model.load_weights(decoder_weights_stripped) logger.debug("=" * 80) logger.debug(f"Audio encoder weights loaded: {len(audio_encoder_loaded)}") logger.debug(f"Audio projector weights loaded: {len(audio_projector_loaded)}") logger.debug( f"Decoder weights passed to language_model: {len(decoder_weights)}" ) logger.debug(f"Skipped weights: {len(skipped_weights)}") encoder_skipped = [s for s in skipped_weights if "audio_encoder" in s] projector_skipped = [s for s in skipped_weights if "audio_projector" in s] if projector_skipped: logger.debug("Skipped audio_projector weights:") for s in projector_skipped: logger.debug(f" {s}") if encoder_skipped: logger.debug(f"Skipped audio_encoder weights: {len(encoder_skipped)}") non_bias_skipped = [s for s in encoder_skipped if "bias" not in s] if non_bias_skipped: logger.debug(" First 10 non-bias skipped:") for s in non_bias_skipped[:10]: logger.debug(f" {s}") logger.debug("=" * 80) def get_embed_and_head(self): return ( self.language_model.model.embed_tokens.weight, self.language_model.lm_head.weight, ) EntryClass = [MiDashengLMModel]