TransWithAI/Step-Audio-R1.1-NVFP4
112
1from typing import Iterable, Optional, Tuple2 3import librosa4import torch5import torch.nn.functional as F6import torchaudio7from torch import Tensor, nn8from transformers import PreTrainedModel, Qwen2Model9from transformers.generation.utils import GenerationMixin10from transformers.modeling_outputs import CausalLMOutputWithPast11 12from .configuration_step_audio_2 import StepAudio2Config13 14 15def _mel_filters(n_mels: int) -> torch.Tensor:16 """Load the mel filterbank matrix for projecting STFT into a Mel spectrogram."""17 assert n_mels in {80, 128}, f"Unsupported n_mels: {n_mels}"18 if n_mels == 128:19 return torch.from_numpy(librosa.filters.mel(sr=16000, n_fft=400, n_mels=128))20 else:21 return torch.from_numpy(librosa.filters.mel(sr=16000, n_fft=400, n_mels=80))22 23 24def load_audio(file_path, target_rate=16000, max_length=None):25 """26 Open an audio file and read as mono waveform, resampling as necessary27 If max_length is provided, truncate the audio to that length28 """29 waveform, sample_rate = torchaudio.load(file_path)30 if sample_rate != target_rate:31 waveform = torchaudio.transforms.Resample(orig_freq=sample_rate, new_freq=target_rate)(waveform)32 audio = waveform[0] # get the first channel33 34 # Truncate audio if it exceeds max_length35 if max_length is not None and audio.shape[0] > max_length:36 audio = audio[:max_length]37 38 return audio39 40def log_mel_spectrogram(audio, n_mels=128, padding=479, device=None):41 """42 Compute the log-Mel spectrogram with specific padding for StepAudio43 """44 if not torch.is_tensor(audio):45 if isinstance(audio, str):46 audio = load_audio(audio)47 audio = torch.from_numpy(audio)48 if device is not None:49 audio = audio.to(device)50 if padding > 0:51 audio = F.pad(audio, (0, padding))52 window = torch.hann_window(400).to(audio.device)53 stft = torch.stft(audio, 400, 160, window=window, return_complex=True)54 magnitudes = stft[..., :-1].abs() ** 255 filters = _mel_filters(n_mels)56 mel_spec = filters @ magnitudes57 58 log_spec = torch.clamp(mel_spec, min=1e-10).log10()59 log_spec = torch.maximum(log_spec, log_spec.max() - 8.0)60 log_spec = (log_spec + 4.0) / 4.061 return log_spec62 63def compute_token_num(max_feature_len):64 # First, audio goes through encoder:65 # 1. conv1: kernel=3, stride=1, padding=1 -> size unchanged66 # 2. conv2: kernel=3, stride=2, padding=1 -> size/267 # 3. avg_pooler: kernel=2, stride=2 -> size/268 max_feature_len = max_feature_len - 2 # remove padding69 encoder_output_dim = (max_feature_len + 1) // 2 // 2 # after conv2 and avg_pooler70 71 # Then through adaptor (parameters from config file):72 padding = 173 kernel_size = 3 # from config: audio_encoder_config.kernel_size74 stride = 2 # from config: audio_encoder_config.adapter_stride75 adapter_output_dim = (encoder_output_dim + 2 * padding - kernel_size) // stride + 176 return adapter_output_dim77 78def make_non_pad_mask(lengths: torch.Tensor, max_len: int = 0) -> torch.Tensor:79 """Make mask tensor containing indices of non-padded part.80 81 The sequences in a batch may have different lengths. To enable82 batch computing, padding is need to make all sequence in same83 size. To avoid the padding part pass value to context dependent84 block such as attention or convolution , this padding part is85 masked.86 87 1 for non-padded part and 0 for padded part.88 89 Parameters90 ----------91 lengths (torch.Tensor): Batch of lengths (B,).92 93 Returns:94 -------95 torch.Tensor: Mask tensor containing indices of padded part (B, max_T).96 97 Examples:98 >>> import torch99 >>> import s3tokenizer100 >>> lengths = torch.tensor([5, 3, 2])101 >>> masks = s3tokenizer.make_non_pad_mask(lengths)102 masks = [[1, 1, 1, 1, 1],103 [1, 1, 1, 0, 0],104 [1, 1, 0, 0, 0]]105 """106 batch_size = lengths.size(0)107 max_len = max_len if max_len > 0 else lengths.max().item()108 seq_range = torch.arange(0,109 max_len,110 dtype=torch.int64,111 device=lengths.device)112 seq_range_expand = seq_range.unsqueeze(0).expand(batch_size, max_len)113 seq_length_expand = lengths.unsqueeze(-1)114 mask = seq_range_expand >= seq_length_expand115 return ~mask116 117def mask_to_bias(mask: torch.Tensor, dtype: torch.dtype) -> torch.Tensor:118 """Convert bool-tensor to float-tensor for flash attention.119 120 Parameters121 ----------122 lengths (torch.Tensor): Batch of lengths (B, ?).123 124 Returns:125 -------126 torch.Tensor: Mask tensor containing indices of padded part (B, ?).127 128 Examples:129 >>> import torch130 >>> import s3tokenizer131 >>> lengths = torch.tensor([5, 3, 2])132 >>> masks = s3tokenizer.make_non_pad_mask(lengths)133 masks = [[1, 1, 1, 1, 1],134 [1, 1, 1, 0, 0],135 [1, 1, 0, 0, 0]]136 >>> new_masks = s3tokenizer.mask_to_bias(masks, torch.float32)137 new_masks = [[-0.0000e+00, -0.0000e+00, -0.0000e+00, -0.0000e+00, -0.0000e+00],138 [-0.0000e+00, -0.0000e+00, -0.0000e+00, -1.0000e+10, -1.0000e+10],139 [-0.0000e+00, -0.0000e+00, -1.0000e+10, -1.0000e+10, -1.0000e+10]]140 """141 assert mask.dtype == torch.bool142 assert dtype in [torch.float32, torch.bfloat16, torch.float16]143 mask = mask.to(dtype)144 # attention mask bias145 # NOTE(Mddct): torch.finfo jit issues146 # chunk_masks = (1.0 - chunk_masks) * torch.finfo(dtype).min147 mask = (1.0 - mask) * -1.0e+10148 return mask149 150class LayerNorm(nn.LayerNorm):151 def forward(self, input: Tensor) -> Tensor:152 return super().forward(input).type(input.dtype)153 154class Linear(nn.Linear):155 def forward(self, input: Tensor) -> Tensor:156 return F.linear(157 input,158 self.weight.to(input.dtype),159 None if self.bias is None else self.bias.to(input.dtype),160 )161 162class Conv1d(nn.Conv1d):163 def _conv_forward(164 self, input: Tensor, weight: Tensor, bias: Optional[Tensor]165 ) -> Tensor:166 return super()._conv_forward(167 input, weight.to(input.dtype), None if bias is None else bias.to(input.dtype)168 )169 170class MultiHeadAttention(nn.Module):171 def __init__(self, n_state: int, n_head: int):172 super().__init__()173 self.n_head = n_head174 self.query = Linear(n_state, n_state)175 self.key = Linear(n_state, n_state, bias=False)176 self.value = Linear(n_state, n_state)177 self.out = Linear(n_state, n_state)178 179 def forward(180 self,181 x: Tensor,182 mask: Optional[Tensor] = None,183 ):184 q = self.query(x)185 k = self.key(x)186 v = self.value(x)187 188 wv, qk = self.qkv_attention(q, k, v, mask)189 return self.out(wv), qk190 191 def qkv_attention(192 self, q: Tensor, k: Tensor, v: Tensor, mask: Optional[Tensor] = None193 ):194 _, T, D = q.shape195 scale = (D // self.n_head) ** -0.25196 q = q.view(*q.shape[:2], self.n_head, -1).permute(0, 2, 1, 3) * scale197 k = k.view(*k.shape[:2], self.n_head, -1).permute(0, 2, 3, 1) * scale198 v = v.view(*v.shape[:2], self.n_head, -1).permute(0, 2, 1, 3)199 200 qk = q @ k # (B, n_head, T, T)201 if mask is not None:202 qk = qk + mask203 qk = qk.float()204 205 w = F.softmax(qk, dim=-1).to(q.dtype)206 return (w @ v).permute(0, 2, 1, 3).flatten(start_dim=2), qk.detach()207 208class ResidualAttentionBlock(nn.Module):209 def __init__(self, n_state: int, n_head: int):210 super().__init__()211 212 self.attn = MultiHeadAttention(n_state, n_head)213 self.attn_ln = LayerNorm(n_state)214 215 n_mlp = n_state * 4216 self.mlp = nn.Sequential(217 Linear(n_state, n_mlp), nn.GELU(), Linear(n_mlp, n_state)218 )219 self.mlp_ln = LayerNorm(n_state)220 221 def forward(222 self,223 x: Tensor,224 mask: Optional[Tensor] = None,225 ):226 x = x + self.attn(self.attn_ln(x.contiguous()), mask=mask)[0]227 x = x + self.mlp(self.mlp_ln(x.contiguous()))228 return x229 230class AudioEncoder(nn.Module):231 def __init__(232 self, n_mels: int, n_ctx: int, n_state: int, n_head: int, n_layer: int233 ):234 super().__init__()235 self.conv1 = Conv1d(n_mels, n_state, kernel_size=3, padding=1)236 self.conv2 = Conv1d(n_state, n_state, kernel_size=3, stride=2, padding=1)237 self.positional_embedding = nn.Embedding(n_ctx, n_state)238 self.positional_embedding.requires_grad_(False)239 self.blocks: Iterable[ResidualAttentionBlock] = nn.ModuleList(240 [ResidualAttentionBlock(n_state, n_head) for _ in range(n_layer)]241 )242 self.avg_pooler = nn.AvgPool1d(2, stride=2)243 self.after_norm = LayerNorm(n_state)244 self.gradient_checkpointing = False245 246 def forward(self, x: Tensor, x_len: Tensor) -> Tuple[Tensor, Tensor]:247 T = x.size(-1)248 x = F.gelu(self.conv1(x))249 x = F.gelu(self.conv2(x))250 x = x.permute(0, 2, 1) # (B, T // 2, n_state)251 mask = make_non_pad_mask(x_len, T).unsqueeze(1) # (B, 1, T)252 mask = mask_to_bias(mask[:, :, (T + 1) % 2::2], x.dtype) # (B, 1, T // 2)253 x = (x + self.positional_embedding.weight[:x.shape[1], :]).to(x.dtype)254 for block in self.blocks:255 if self.gradient_checkpointing and self.training:256 x = torch.utils.checkpoint.checkpoint(block, x, mask.unsqueeze(1))257 else:258 x = block(x, mask.unsqueeze(1))259 x = x.permute(0, 2, 1)260 x = self.avg_pooler(x)261 x = x.permute(0, 2, 1)262 x_len = (x_len + 1) // 2 // 2263 x = self.after_norm(x.contiguous())264 return x, x_len265 266class Adaptor(nn.Module):267 def __init__(268 self,269 n_state: int = 1280,270 n_hidden: int = 3072,271 kernel_size: int = 7,272 stride: int = 4273 ):274 super().__init__()275 self.stride = stride276 if self.stride != -1:277 # print("self.stride: {}".format(self.stride))278 self.conv = Conv1d(n_state, n_state, kernel_size, stride, padding=1)279 self.linear1 = nn.Linear(n_state, 2048)280 self.relu = nn.ReLU()281 self.linear2 = nn.Linear(2048, n_hidden)282 self.gradient_checkpointing = False283 284 def forward(self, x: Tensor) -> Tuple[Tensor]:285 T = x.size(-1)286 if self.stride != -1:287 if self.gradient_checkpointing and self.training:288 x = torch.utils.checkpoint.checkpoint(self.conv, x.permute(0, 2, 1))289 x = x.permute(0, 2, 1)290 else:291 x = x.permute(0, 2, 1)292 x = F.gelu(self.conv(x))293 x = x.permute(0, 2, 1)294 if self.gradient_checkpointing and self.training:295 x = torch.utils.checkpoint.checkpoint(self.linear1, x)296 x = torch.utils.checkpoint.checkpoint(self.relu, x)297 x = torch.utils.checkpoint.checkpoint(self.linear2, x)298 else:299 x = self.linear1(x)300 x = self.relu(x)301 x = self.linear2(x)302 return x303 304class StepAudio2ForCausalLM(PreTrainedModel, GenerationMixin):305 config_class = StepAudio2Config306 main_input_name = "input_ids"307 # Important: Add this attribute to make HF recognize it as a model with generation capability308 # _keys_to_ignore_on_load_missing = ["lm_head.weight"]309 supports_gradient_checkpointing = True # 新增,声明支持gradient checkpointing310 311 def __init__(self, config: StepAudio2Config):312 super().__init__(config)313 # Handle torch_dtype with default fallback314 if hasattr(config, 'torch_dtype') and config.torch_dtype is not None:315 if isinstance(config.torch_dtype, str):316 dtype = getattr(torch, config.torch_dtype)317 else:318 dtype = config.torch_dtype319 else:320 # Default to bfloat16 if not specified321 dtype = torch.bfloat16322 self.model = Qwen2Model(config.text_config)323 self.bf16 = dtype==torch.bfloat16324 self.encoder = AudioEncoder(325 config.audio_encoder_config.n_mels, config.audio_encoder_config.n_audio_ctx, config.audio_encoder_config.n_audio_state,326 config.audio_encoder_config.n_audio_head, config.audio_encoder_config.n_audio_layer327 )328 self.adapter = Adaptor(329 config.audio_encoder_config.n_audio_state, config.audio_encoder_config.llm_dim,330 config.audio_encoder_config.kernel_size, config.audio_encoder_config.adapter_stride331 )332 if self.bf16:333 self.encoder = self.encoder.bfloat16()334 self.adapter = self.adapter.bfloat16()335 self.lm_head = torch.nn.Linear(336 config.text_config.hidden_size,337 config.text_config.vocab_size,338 bias=False,339 dtype=dtype340 )341 self.post_init()342 343 def forward(344 self,345 input_ids=None,346 wavs=None,347 wav_lens=None,348 attention_mask=None,349 **kwargs350 ):351 hidden_states = self.model.embed_tokens(input_ids)352 if wavs is not None:353 if self.bf16:354 wavs = wavs.bfloat16()355 out, feat_lens = self.encoder(wavs, wav_lens)356 out = self.adapter(out)357 feat_lens = (feat_lens - 1) // 2 + 1358 insert_location = torch.nonzero(input_ids == 151688)359 insert_location[:,1] += 1360 for idx in range(len(insert_location)):361 i,s = insert_location[idx]362 hidden_states[i][s : s+feat_lens[idx]] = out[idx][:feat_lens[idx]]363 364 x = self.model(inputs_embeds=hidden_states, attention_mask=attention_mask)[0]365 logits = self.lm_head(x)366 return CausalLMOutputWithPast(367 logits=logits,368 past_key_values=None,369 hidden_states=None,370 attentions=None371 )372 373 def get_input_embeddings(self):374 """Return the model's input embeddings - required for GenerationMixin"""375 return self.model.embed_tokens376 377 def get_output_embeddings(self):378 """Return the model's output embeddings (LM head) - required for GenerationMixin"""379 return self.lm_head380 381 def prepare_inputs_for_generation(self, input_ids, attention_mask=None, **kwargs):382 """Prepare inputs for generation - required for GenerationMixin"""383 # Keep the wavs and wav_lens from the initial call384 wavs = kwargs.get("wavs", None)385 wav_lens = kwargs.get("wav_lens", None)386 387 # For generation steps after the first, we don't need to process audio again388 # because the audio tokens have already been replaced in the input sequence389 if "past_key_values" in kwargs and kwargs["past_key_values"] is not None:390 # We're in a generation step, no need to process audio again391 return {392 "input_ids": input_ids,393 "attention_mask": attention_mask,394 "past_key_values": kwargs.get("past_key_values")395 }396 397 # First generation step, include audio processing398 return {399 "input_ids": input_ids,400 "attention_mask": attention_mask,401 "wavs": wavs,402 "wav_lens": wav_lens403 }404 405 def _reorder_cache(self, past_key_values, beam_idx):406 """Reorder the cache for beam search - required for GenerationMixin if using beam search"""407 # If you're not using past_key_values or beam search, this can be a simple pass-through408 # Otherwise implement according to your model's cache structure409 return past_key_values410 411 def _set_gradient_checkpointing(self, module, value=False):412 # For Qwen2Model413 if hasattr(self.model, 'gradient_checkpointing'):414 self.model.gradient_checkpointing = value415 416 # Add the missing _gradient_checkpointing_func method to Qwen2Model417 # This is what Qwen2Model tries to use when gradient_checkpointing=True418 if value and not hasattr(self.model, '_gradient_checkpointing_func'):419 def _gradient_checkpointing_func(module_to_run, *args, **kwargs):420 # This function wraps torch.utils.checkpoint.checkpoint421 # and is used by Qwen2Model to perform checkpointing422 return torch.utils.checkpoint.checkpoint(module_to_run, *args, **kwargs)423 424 self.model._gradient_checkpointing_func = _gradient_checkpointing_func425 426 # For custom encoder and adapter427 if hasattr(self.encoder, 'gradient_checkpointing'):428 self.encoder.gradient_checkpointing = value429 if hasattr(self.adapter, 'gradient_checkpointing'):430 self.adapter.gradient_checkpointing = value431 