main
py 745 lines 29.6 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 # ruff: noqa: E501
4 """Dots-path speech encoder for inference only (single GPU).
5
6 Ported from cybertron_alm ``dots_audio_encoder/modeling_whisper.py``.
7 Upstream ``WhisperEncoder`` is exposed as :class:`DotsSpeechEncoder`.
8 """
9
10 import math
11 from typing import Any, ClassVar
12
13 import torch
14 from torch import nn
15 from transformers.activations import ACT2FN
16 from transformers.modeling_outputs import BaseModelOutput
17 from transformers.modeling_utils import PreTrainedModel
18 from transformers.models.whisper.configuration_whisper import WhisperConfig
19 from transformers.utils import logging
20
21 from vllm.vllm_flash_attn import flash_attn_varlen_func, is_fa_version_supported
22
23 _FLASH_ATTN_VERSION = 3 if is_fa_version_supported(3) else 2
24
25 logger = logging.get_logger(__name__)
26
27 __all__ = ["DotsSpeechEncoder"]
28
29
30 class RMSNorm(nn.Module):
31 def __init__(self, dim: int, eps: float = 1e-6):
32 super().__init__()
33 self.weight = nn.Parameter(torch.ones(dim))
34 self.eps = eps
35
36 def forward(self, x: torch.Tensor) -> torch.Tensor:
37 var = x.pow(2).mean(-1, keepdim=True)
38 x = x * torch.rsqrt(var + self.eps)
39 return self.weight * x
40
41
42 def swiglu(x: torch.Tensor) -> torch.Tensor:
43 x1, x2 = x.chunk(2, dim=-1)
44 return torch.nn.functional.silu(x1) * x2
45
46
47 class RotaryEmbedding(nn.Module):
48 def __init__(
49 self,
50 head_dim: int,
51 rope_parameters: dict,
52 base_seq_len: int = 0,
53 ):
54 super().__init__()
55 self.partial_rotary_factor = float(
56 rope_parameters.get("partial_rotary_factor", 1.0)
57 )
58 self.rope_theta = float(rope_parameters.get("rope_theta", 10000.0))
59 self.rope_type = rope_parameters.get("rope_type", "default")
60 rotary_dim = int(head_dim * self.partial_rotary_factor)
61 # keep even rotary dimension to align cos/sin pairs
62 self.rotary_dim = (rotary_dim // 2) * 2
63 self.attention_scaling = 1.0
64 if self.rope_type != "default":
65 logger.warning(
66 "RoPE type %s is not implemented in WhisperLv3; using default",
67 self.rope_type,
68 )
69 if self.rotary_dim > 0:
70 inv_freq = 1.0 / (
71 self.rope_theta
72 ** (
73 torch.arange(0, self.rotary_dim, 2, dtype=torch.float)
74 / max(self.rotary_dim, 1)
75 )
76 )
77 else:
78 inv_freq = torch.tensor([])
79 self.register_buffer("inv_freq", inv_freq, persistent=False)
80 self.base_seq_len = max(0, int(base_seq_len))
81 self._cache: (
82 tuple[int, torch.dtype, torch.device, torch.Tensor, torch.Tensor] | None
83 ) = None
84
85 @torch.no_grad()
86 def get_cos_sin(
87 self, position_ids: torch.Tensor, dtype: torch.dtype, device: torch.device
88 ) -> tuple[torch.Tensor | None, torch.Tensor | None]:
89 if self.rotary_dim == 0:
90 return None, None
91 seq_len = position_ids.shape[-1]
92 if position_ids.shape[0] == 1 and self._cache is not None:
93 cached_seq_len, cached_dtype, cached_device, cached_cos, cached_sin = (
94 self._cache
95 )
96 if (
97 cached_seq_len >= seq_len
98 and cached_dtype == dtype
99 and cached_device == device
100 ):
101 return cached_cos[:, :seq_len, :], cached_sin[:, :seq_len, :]
102
103 if position_ids.shape[0] == 1:
104 cache_seq_len = max(seq_len, self.base_seq_len)
105 position_ids = torch.arange(cache_seq_len, device=device)[None, :]
106 # [1, D/2, 1]
107 if self.inv_freq.dtype != torch.float32:
108 inv_freq = 1.0 / (
109 self.rope_theta
110 ** (
111 torch.arange(
112 0, self.rotary_dim, 2, dtype=torch.float, device=device
113 )
114 / max(self.rotary_dim, 1)
115 )
116 )
117 else:
118 inv_freq = self.inv_freq.to(device=device)
119 inv_freq_expanded = inv_freq[None, :, None]
120 # [B, 1, T]
121 position_ids_expanded = position_ids[:, None, :].float()
122 # Force float32 since bfloat16 loses precision on long contexts
123 # See https://github.com/huggingface/transformers/pull/29285
124 device_type = device.type if isinstance(device, torch.device) else "cpu"
125 with torch.autocast(device_type=device_type, enabled=False):
126 freqs = (
127 inv_freq_expanded.float() @ position_ids_expanded.float()
128 ).transpose(1, 2)
129 emb = torch.cat((freqs, freqs), dim=-1)
130 cos = emb.cos() * self.attention_scaling
131 sin = emb.sin() * self.attention_scaling
132 cos = cos.to(dtype)
133 sin = sin.to(dtype)
134 if position_ids.shape[0] == 1:
135 self._cache = (position_ids.shape[-1], dtype, device, cos, sin)
136 return cos[:, :seq_len, :], sin[:, :seq_len, :]
137 return cos, sin
138
139
140 def rotate_half(x: torch.Tensor) -> torch.Tensor:
141 x1 = x[..., : x.shape[-1] // 2]
142 x2 = x[..., x.shape[-1] // 2 :]
143 return torch.cat((-x2, x1), dim=-1)
144
145
146 def apply_rotary_pos_emb(
147 q: torch.Tensor,
148 k: torch.Tensor,
149 cos: torch.Tensor | None,
150 sin: torch.Tensor | None,
151 ) -> tuple[torch.Tensor, torch.Tensor]:
152 if cos is None or sin is None:
153 return q, k
154
155 if q.dim() == 4:
156 # [B, T, H, D]
157 rotary_dim = cos.shape[-1]
158 cos = cos.unsqueeze(2) # [B, T, 1, rotary_dim]
159 sin = sin.unsqueeze(2)
160 q_rot, q_pass = q[..., :rotary_dim], q[..., rotary_dim:]
161 k_rot, k_pass = k[..., :rotary_dim], k[..., rotary_dim:]
162 q_embed = (q_rot * cos) + (rotate_half(q_rot) * sin)
163 k_embed = (k_rot * cos) + (rotate_half(k_rot) * sin)
164 return torch.cat([q_embed, q_pass], dim=-1), torch.cat(
165 [k_embed, k_pass], dim=-1
166 )
167
168 if q.dim() == 3:
169 # [tokens, H, D] (varlen flattened)
170 rotary_dim = cos.shape[-1]
171 cos = cos.unsqueeze(1) # [tokens, 1, rotary_dim]
172 sin = sin.unsqueeze(1)
173 q_rot, q_pass = q[..., :rotary_dim], q[..., rotary_dim:]
174 k_rot, k_pass = k[..., :rotary_dim], k[..., rotary_dim:]
175 q_embed = (q_rot * cos) + (rotate_half(q_rot) * sin)
176 k_embed = (k_rot * cos) + (rotate_half(k_rot) * sin)
177 return torch.cat([q_embed, q_pass], dim=-1), torch.cat(
178 [k_embed, k_pass], dim=-1
179 )
180
181 return q, k
182
183
184 class WhisperPositionalEmbedding(nn.Embedding):
185 def __init__(
186 self, num_positions: int, embedding_dim: int, padding_idx: int | None = None
187 ):
188 super().__init__(num_positions, embedding_dim)
189
190 def forward(self, input_ids, past_key_values_length=0):
191 return self.weight[
192 past_key_values_length : past_key_values_length + input_ids.shape[1]
193 ]
194
195
196 class WhisperAttention(nn.Module):
197 """Multi-headed attention from 'Attention Is All You Need' paper."""
198
199 def __init__(
200 self,
201 embed_dim: int,
202 num_heads: int,
203 dropout: float = 0.0,
204 is_decoder: bool = False,
205 bias: bool = True,
206 ):
207 super().__init__()
208 self.embed_dim = embed_dim
209 self.num_heads = num_heads
210 self.dropout = dropout
211 self.head_dim = embed_dim // num_heads
212
213 if (self.head_dim * num_heads) != self.embed_dim:
214 raise ValueError(
215 f"embed_dim must be divisible by num_heads (got `embed_dim`: {self.embed_dim}"
216 f" and `num_heads`: {num_heads})."
217 )
218 self.scaling = self.head_dim**-0.5
219 self.is_decoder = is_decoder
220
221 self.k_proj = nn.Linear(embed_dim, embed_dim, bias=False)
222 self.v_proj = nn.Linear(embed_dim, embed_dim, bias=bias)
223 self.q_proj = nn.Linear(embed_dim, embed_dim, bias=bias)
224 self.out_proj = nn.Linear(embed_dim, embed_dim, bias=bias)
225
226 def forward_flash_attn(
227 self,
228 hidden_states,
229 cu_seqlens_q=None,
230 cu_seqlens_kv=None,
231 max_seqlen_q=None,
232 max_seqlen_kv=None,
233 output_attentions=False,
234 rotary_cos=None,
235 rotary_sin=None,
236 ):
237 """Dense eager attention with SGLang FA3 for packed variable-length input."""
238 query_states = self.q_proj(hidden_states)
239 key_states = self.k_proj(hidden_states)
240 value_states = self.v_proj(hidden_states)
241 if cu_seqlens_q is None:
242 bsz, tgt_len, _ = hidden_states.size()
243 query_states = query_states.view(
244 bsz, tgt_len, self.num_heads, self.head_dim
245 )
246 key_states = key_states.view(bsz, tgt_len, self.num_heads, self.head_dim)
247 value_states = value_states.view(
248 bsz, tgt_len, self.num_heads, self.head_dim
249 )
250 if rotary_cos is not None and rotary_sin is not None:
251 cos = rotary_cos[:, :tgt_len, :]
252 sin = rotary_sin[:, :tgt_len, :]
253 query_states, key_states = apply_rotary_pos_emb(
254 query_states, key_states, cos, sin
255 )
256 attn_output, attn_probs = self._eager_attention(
257 query_states, key_states, value_states, output_attentions
258 )
259 attn_output = attn_output.view(bsz, tgt_len, self.embed_dim)
260 else:
261 query_states = query_states.view(-1, self.num_heads, self.head_dim)
262 key_states = key_states.view(-1, self.num_heads, self.head_dim)
263 value_states = value_states.view(-1, self.num_heads, self.head_dim)
264 if rotary_cos is not None and rotary_sin is not None:
265 query_states, key_states = apply_rotary_pos_emb(
266 query_states, key_states, rotary_cos, rotary_sin
267 )
268 result = flash_attn_varlen_func(
269 query_states,
270 key_states,
271 value_states,
272 cu_seqlens_q=cu_seqlens_q,
273 cu_seqlens_k=cu_seqlens_kv,
274 max_seqlen_q=max_seqlen_q,
275 max_seqlen_k=max_seqlen_kv,
276 softmax_scale=self.scaling,
277 causal=self.is_decoder,
278 return_softmax_lse=output_attentions,
279 fa_version=_FLASH_ATTN_VERSION,
280 )
281 if isinstance(result, tuple):
282 attn_output = result[0]
283 attn_probs = result[1] if output_attentions else None
284 else:
285 attn_output = result
286 attn_probs = None
287 attn_output = attn_output.view(-1, self.embed_dim)
288
289 return self.out_proj(attn_output), attn_probs
290
291 def _eager_attention(self, query, key, value, output_attentions):
292 query = query.transpose(1, 2)
293 key = key.transpose(1, 2)
294 value = value.transpose(1, 2)
295 scores = torch.matmul(query, key.transpose(-2, -1)) * self.scaling
296 if self.is_decoder:
297 seq_len = scores.shape[-1]
298 causal_mask = torch.ones(
299 seq_len, seq_len, dtype=torch.bool, device=scores.device
300 ).triu(1)
301 scores.masked_fill_(causal_mask, torch.finfo(scores.dtype).min)
302 probabilities = torch.softmax(scores, dim=-1, dtype=torch.float32).to(
303 query.dtype
304 )
305 output = torch.matmul(probabilities, value).transpose(1, 2)
306 return output, probabilities if output_attentions else None
307
308
309 # Copied from transformers.models.mbart.modeling_mbart.MBartEncoderLayer with MBart->Whisper
310 class WhisperEncoderLayer(nn.Module):
311 def __init__(self, config: WhisperConfig):
312 super().__init__()
313 self.embed_dim = config.d_model
314 use_causal = getattr(config, "use_causal", False)
315 self.self_attn = WhisperAttention(
316 embed_dim=self.embed_dim,
317 num_heads=config.encoder_attention_heads,
318 dropout=config.attention_dropout,
319 is_decoder=use_causal, # enable causal attention when use_causal=True
320 )
321 self.use_causal = use_causal
322 norm_cls = RMSNorm if getattr(config, "use_rms_norm", False) else nn.LayerNorm
323 self.self_attn_layer_norm = norm_cls(self.embed_dim)
324 self.dropout = config.dropout
325 self.activation_fn = (
326 ACT2FN[config.activation_function]
327 if config.activation_function != "swiglu"
328 else swiglu
329 )
330 self.use_swiglu = config.activation_function == "swiglu"
331 self.activation_dropout = config.activation_dropout
332 ffn_dim = config.encoder_ffn_dim
333 fc1_out = ffn_dim * 2 if self.use_swiglu else ffn_dim
334 self.fc1 = nn.Linear(self.embed_dim, fc1_out)
335 self.fc2 = nn.Linear(ffn_dim, self.embed_dim)
336 self.final_layer_norm = norm_cls(self.embed_dim)
337
338 def forward(
339 self,
340 hidden_states: torch.Tensor,
341 cu_seqlens_q: torch.Tensor = None,
342 cu_seqlens_kv: torch.Tensor = None,
343 max_seqlen_q: int | None = None,
344 max_seqlen_kv: int | None = None,
345 output_attentions: bool = False,
346 rotary_cos: torch.Tensor | None = None,
347 rotary_sin: torch.Tensor | None = None,
348 ) -> tuple[Any, ...]:
349 """Args:
350 hidden_states: Input to the layer of shape `(batch, seq_len,
351 embed_dim)`, or `(total_tokens, embed_dim)` when `cu_seqlens_q`
352 is given.
353 cu_seqlens_q: Cumulative query sequence lengths for packed
354 variable-length input. If `None`, the input is treated as a
355 padded batch.
356 cu_seqlens_kv: Cumulative key/value sequence lengths for packed
357 variable-length input.
358 max_seqlen_q: Longest query sequence in the packed batch.
359 max_seqlen_kv: Longest key/value sequence in the packed batch.
360 output_attentions: Whether to also return the attention weights.
361 rotary_cos: Cosine component of the rotary position embedding.
362 rotary_sin: Sine component of the rotary position embedding.
363
364 Returns:
365 A tuple of the output hidden states, followed by the attention
366 weights if `output_attentions` is `True`.
367
368 """
369 residual = hidden_states
370 hidden_states = self.self_attn_layer_norm(hidden_states)
371
372 hidden_states, attn_weights = self.self_attn.forward_flash_attn(
373 hidden_states=hidden_states,
374 cu_seqlens_q=cu_seqlens_q,
375 cu_seqlens_kv=cu_seqlens_kv,
376 max_seqlen_q=max_seqlen_q,
377 max_seqlen_kv=max_seqlen_kv,
378 output_attentions=output_attentions,
379 rotary_cos=rotary_cos,
380 rotary_sin=rotary_sin,
381 )
382 hidden_states = nn.functional.dropout(
383 hidden_states, p=self.dropout, training=self.training
384 )
385 hidden_states = residual + hidden_states
386
387 residual = hidden_states
388 hidden_states = self.final_layer_norm(hidden_states)
389 if self.use_swiglu:
390 hidden_states = swiglu(self.fc1(hidden_states))
391 else:
392 hidden_states = self.activation_fn(self.fc1(hidden_states))
393 hidden_states = nn.functional.dropout(
394 hidden_states, p=self.activation_dropout, training=self.training
395 )
396 hidden_states = self.fc2(hidden_states)
397 hidden_states = nn.functional.dropout(
398 hidden_states, p=self.dropout, training=self.training
399 )
400 hidden_states = residual + hidden_states
401
402 if hidden_states.dtype == torch.float16 and (
403 torch.isinf(hidden_states).any() or torch.isnan(hidden_states).any()
404 ):
405 clamp_value = torch.finfo(hidden_states.dtype).max - 1000
406 hidden_states = torch.clamp(
407 hidden_states, min=-clamp_value, max=clamp_value
408 )
409
410 outputs: tuple[Any, ...] = (hidden_states,)
411
412 if output_attentions:
413 outputs += (attn_weights,)
414
415 return outputs
416
417
418 class DotsSpeechPreTrainedModel(PreTrainedModel):
419 config_class = WhisperConfig
420 base_model_prefix = "model"
421 main_input_name = "input_features"
422 supports_gradient_checkpointing = False
423 _no_split_modules: ClassVar[list[str]] = ["WhisperEncoderLayer"]
424
425 def _init_weights(self, module):
426 std = self.config.init_std
427 if isinstance(module, (nn.Linear, nn.Conv1d, nn.Conv2d)):
428 module.weight.data.normal_(mean=0.0, std=std)
429 if module.bias is not None:
430 module.bias.data.zero_()
431 elif isinstance(module, nn.Embedding):
432 module.weight.data.normal_(mean=0.0, std=std)
433 if module.padding_idx is not None:
434 module.weight.data[module.padding_idx].zero_()
435
436
437 class DotsSpeechEncoder(DotsSpeechPreTrainedModel):
438 """Transformer encoder consisting of *config.encoder_layers* self attention layers. Each layer is a
439 [`WhisperEncoderLayer`].
440
441 Args:
442 config: WhisperConfig
443
444 """
445
446 def __init__(self, config: WhisperConfig):
447 super().__init__(config)
448 self.dropout = config.dropout
449
450 embed_dim = config.d_model
451 self.use_latent_input = getattr(config, "use_latent_input", False)
452 self.use_causal = getattr(config, "use_causal", False)
453 self.use_conv2d_stem = getattr(config, "use_conv2d_stem", False)
454 latent_dim = getattr(config, "latent_dim", None)
455 if self.use_latent_input and latent_dim is None:
456 raise ValueError(
457 "DotsSpeechEncoder: use_latent_input=True requires config.latent_dim"
458 )
459 if self.use_conv2d_stem and self.use_latent_input:
460 raise ValueError(
461 "DotsSpeechEncoder: use_conv2d_stem and use_latent_input are mutually exclusive"
462 )
463 self.num_mel_bins = (
464 latent_dim if self.use_latent_input and latent_dim is not None else 128
465 )
466 self.padding_idx = config.pad_token_id
467 self.max_source_positions = config.max_source_positions
468 self.embed_scale = math.sqrt(embed_dim) if config.scale_embedding else 1.0
469
470 if self.use_conv2d_stem:
471 # Conv2D stem: 3 layers of stride-2 for 8x downsampling
472 dhs = getattr(config, "downsample_hidden_size", 480)
473 # Causal: keep freq padding, remove time padding (handled by pad(14,0) in forward)
474 conv_padding = (1, 0) if self.use_causal else 1
475 self.conv2d1 = nn.Conv2d(
476 1, dhs, kernel_size=3, stride=2, padding=conv_padding
477 )
478 self.conv2d2 = nn.Conv2d(
479 dhs, dhs, kernel_size=3, stride=2, padding=conv_padding
480 )
481 self.conv2d3 = nn.Conv2d(
482 dhs, dhs, kernel_size=3, stride=2, padding=conv_padding
483 )
484 # After 3x stride-2 on freq=128: 128→64→32→16; linear projects dhs*16 → embed_dim
485 freq_after = self.num_mel_bins
486 for _ in range(3):
487 freq_after = (freq_after + 1) // 2
488 self.conv_out = nn.Linear(dhs * freq_after, embed_dim, bias=False)
489 self.conv1 = None
490 self.conv2 = None
491 elif self.use_latent_input:
492 # Latent path keeps length (stride=1) and applies GLU after conv1
493 self.conv1 = nn.Conv1d(
494 self.num_mel_bins, embed_dim * 2, kernel_size=3, stride=1, padding=1
495 )
496 self.conv2 = nn.Conv1d(
497 embed_dim, embed_dim, kernel_size=3, stride=1, padding=1
498 )
499 else:
500 self.conv1 = nn.Conv1d(
501 self.num_mel_bins, embed_dim, kernel_size=3, padding=1
502 )
503 self.conv2 = nn.Conv1d(
504 embed_dim, embed_dim, kernel_size=3, stride=2, padding=1
505 )
506
507 self.use_rope = getattr(config, "use_rope", False)
508 rope_parameters = getattr(config, "rope_parameters", {}) or {}
509 self.rope_parameters = rope_parameters
510 if self.use_rope:
511 head_dim = embed_dim // config.encoder_attention_heads
512 self.rotary_embedding = RotaryEmbedding(
513 head_dim,
514 rope_parameters,
515 base_seq_len=self.max_source_positions,
516 )
517 self.embed_positions = None
518 else:
519 self.embed_positions = nn.Embedding(self.max_source_positions, embed_dim)
520
521 self.layers = nn.ModuleList(
522 [WhisperEncoderLayer(config) for _ in range(config.encoder_layers)]
523 )
524 norm_cls = RMSNorm if getattr(config, "use_rms_norm", False) else nn.LayerNorm
525 self.layer_norm = norm_cls(config.d_model)
526
527 self.post_init()
528
529 def get_input_embeddings(self) -> nn.Module:
530 if self.use_conv2d_stem:
531 return self.conv2d1
532 return self.conv1
533
534 def set_input_embeddings(self, value: nn.Module):
535 self.conv1 = value
536
537 @staticmethod
538 def _temporal_mask(feat, valid_lens):
539 """Zero out temporal positions >= valid_lens. feat: [B,C,F,T], valid_lens: [B] tensor."""
540 T = feat.shape[-1]
541 mask = torch.arange(T, device=feat.device)[None, :] < valid_lens[:, None]
542 return mask[:, None, None, :]
543
544 def _conv2d_stem_one_chunk(self, chunk, chunk_valid_mel_lens=None):
545 """Run 3x Conv2d(stride=2) + GELU with per-layer masking."""
546 # Causal: left-pad time by 14 = 2 + 4 + 8 (receptive field of the 3-layer
547 # stride-2 stack mapped back to input time). Combined with conv padding=(1,0),
548 # this makes the whole conv2d stem strictly causal.
549 if self.use_causal:
550 chunk = nn.functional.pad(chunk, (14, 0))
551 if chunk_valid_mel_lens is not None:
552 chunk_valid_mel_lens = chunk_valid_mel_lens + 14
553 # Step 0: mask mel — silence_mel(-1.5) → 0
554 if chunk_valid_mel_lens is not None:
555 chunk = chunk * self._temporal_mask(chunk, chunk_valid_mel_lens)
556 chunk = nn.functional.gelu(self.conv2d1(chunk))
557 # Step 1: mask conv1 output — gelu(conv(0)+bias) → 0
558 if chunk_valid_mel_lens is not None:
559 chunk_valid_mel_lens = (chunk_valid_mel_lens + 1) // 2
560 chunk = chunk * self._temporal_mask(chunk, chunk_valid_mel_lens)
561 chunk = nn.functional.gelu(self.conv2d2(chunk))
562 # Step 2: mask conv2 output — gelu(bias) → 0
563 if chunk_valid_mel_lens is not None:
564 chunk_valid_mel_lens = (chunk_valid_mel_lens + 1) // 2
565 chunk = chunk * self._temporal_mask(chunk, chunk_valid_mel_lens)
566 chunk = nn.functional.gelu(self.conv2d3(chunk))
567 # Step 3: mask conv3 output — gelu(bias) → 0
568 if chunk_valid_mel_lens is not None:
569 chunk_valid_mel_lens = (chunk_valid_mel_lens + 1) // 2
570 chunk = chunk * self._temporal_mask(chunk, chunk_valid_mel_lens)
571 return chunk
572
573 def _forward_conv2d_stem(
574 self, input_features, input_seq_lens=None, audio_sample_lens=None
575 ):
576 """Conv2D stem: 3x Conv2d(stride=2) → 8x downsample. Returns [B, T/8, embed_dim]."""
577 x = input_features.unsqueeze(1) # [B, 1, 128, T]
578
579 if audio_sample_lens is not None:
580 hop_length = 160
581 if not isinstance(audio_sample_lens, torch.Tensor):
582 audio_sample_lens = torch.tensor(audio_sample_lens, device=x.device)
583 valid_mel_lens = audio_sample_lens.to(x.device) // hop_length
584 else:
585 valid_mel_lens = None
586 x = self._conv2d_stem_one_chunk(x, valid_mel_lens)
587 # [B, dhs, F_out, T/8] → [B, T/8, dhs*freq_after] → [B, T/8, embed_dim]
588 batch, channels, frequency, time = x.shape
589 x = x.permute(0, 3, 1, 2).reshape(batch, time, channels * frequency)
590 inputs_embeds = self.conv_out(x)
591
592 return inputs_embeds
593
594 def _forward_conv1d_stem(self, input_features):
595 """Standard Conv1d stem: conv1 + conv2(stride=2), 2x downsample. Returns [B, T/2, embed_dim]."""
596 assert self.conv1 is not None and self.conv2 is not None
597 if self.use_causal:
598 x = nn.functional.pad(input_features, (2, 0))
599 x = nn.functional.gelu(self.conv1(x))
600 x = nn.functional.pad(x, (2, 0))
601 x = nn.functional.gelu(self.conv2(x))
602 else:
603 x = nn.functional.gelu(self.conv1(input_features))
604 x = nn.functional.gelu(self.conv2(x))
605 return x.permute(0, 2, 1)
606
607 def _forward_latent_stem(self, input_features):
608 """Latent input stem: conv1+GLU + conv2, stride=1, no downsample. Returns [B, T, embed_dim]."""
609 assert self.conv1 is not None and self.conv2 is not None
610 if self.use_causal:
611 x = nn.functional.pad(input_features, (2, 0))
612 x = nn.functional.glu(self.conv1(x), dim=1)
613 x = nn.functional.pad(x, (2, 0))
614 x = nn.functional.gelu(self.conv2(x))
615 else:
616 x = nn.functional.glu(self.conv1(input_features), dim=1)
617 x = nn.functional.gelu(self.conv2(x))
618 return x.permute(0, 2, 1)
619
620 def forward(
621 self,
622 input_features,
623 input_seq_lens=None,
624 audio_sample_lens=None,
625 output_attentions=None,
626 output_hidden_states=None,
627 return_dict=None,
628 ):
629 """Mel `[B, n_mels, T]` → encoder hidden states. Inference-only."""
630 output_attentions = (
631 output_attentions
632 if output_attentions is not None
633 else self.config.output_attentions
634 )
635 output_hidden_states = (
636 output_hidden_states
637 if output_hidden_states is not None
638 else self.config.output_hidden_states
639 )
640 return_dict = (
641 return_dict if return_dict is not None else self.config.use_return_dict
642 )
643 if self.use_conv2d_stem:
644 inputs_embeds = self._forward_conv2d_stem(
645 input_features, input_seq_lens, audio_sample_lens
646 )
647 elif self.use_latent_input:
648 inputs_embeds = self._forward_latent_stem(input_features)
649 else:
650 inputs_embeds = self._forward_conv1d_stem(input_features)
651 rotary_cos = None
652 rotary_sin = None
653 position_ids = torch.arange(
654 inputs_embeds.shape[1], device=inputs_embeds.device
655 )[None, :]
656 if self.use_rope:
657 rotary_cos, rotary_sin = self.rotary_embedding.get_cos_sin(
658 position_ids, inputs_embeds.dtype, inputs_embeds.device
659 )
660 hidden_states = inputs_embeds
661 else:
662 assert self.embed_positions is not None
663 embed_pos = self.embed_positions.weight[: inputs_embeds.shape[1]]
664 hidden_states = inputs_embeds + embed_pos
665
666 encoder_states: tuple[torch.Tensor, ...] | None = (
667 () if output_hidden_states else None
668 )
669 all_attentions: tuple[torch.Tensor, ...] | None = (
670 () if output_attentions else None
671 )
672
673 if input_seq_lens is not None:
674 # Build varlen metadata and pack valid tokens without per-sample cat loops.
675 # Read max on whatever device the caller provided: when it is a CPU
676 # tensor this avoids a device->host sync (one per forward).
677 max_seqlen_q = int(input_seq_lens.max().item())
678 input_seq_lens = input_seq_lens.to(
679 device=hidden_states.device, dtype=torch.long
680 )
681 B, S, D = hidden_states.shape
682 max_seqlen_kv = max_seqlen_q
683 cu_seqlens_q = torch.nn.functional.pad(
684 input_seq_lens.cumsum(0, dtype=torch.int32), (1, 0)
685 )
686 cu_seqlens_kv = cu_seqlens_q
687
688 token_positions = torch.arange(S, device=hidden_states.device)[None, :]
689 valid_token_mask = token_positions < input_seq_lens[:, None]
690 hidden_states = hidden_states[valid_token_mask]
691 if rotary_cos is not None and rotary_sin is not None:
692 packed_positions = token_positions.expand(B, S)[valid_token_mask]
693 rotary_cos = rotary_cos.squeeze(0).index_select(0, packed_positions)
694 rotary_sin = rotary_sin.squeeze(0).index_select(0, packed_positions)
695 else:
696 cu_seqlens_q = None
697 cu_seqlens_kv = None
698 max_seqlen_q = None
699 max_seqlen_kv = None
700
701 for encoder_layer in self.layers:
702 if output_hidden_states:
703 assert encoder_states is not None
704 encoder_states = encoder_states + (hidden_states,)
705 layer_outputs = encoder_layer(
706 hidden_states,
707 cu_seqlens_q,
708 cu_seqlens_kv,
709 max_seqlen_q,
710 max_seqlen_kv,
711 output_attentions=output_attentions,
712 rotary_cos=rotary_cos,
713 rotary_sin=rotary_sin,
714 )
715 hidden_states = layer_outputs[0]
716 if output_attentions:
717 assert all_attentions is not None
718 all_attentions = all_attentions + (layer_outputs[1],)
719
720 if input_seq_lens is not None:
721 # Recover packed varlen output directly on device.
722 recover_positions = torch.arange(max_seqlen_q, device=hidden_states.device)[
723 None, :
724 ]
725 recover_mask = recover_positions < input_seq_lens[:, None]
726 recovered = hidden_states.new_zeros(B, max_seqlen_q, D)
727 recovered[recover_mask] = hidden_states
728 hidden_states = recovered
729
730 hidden_states = self.layer_norm(hidden_states)
731 if output_hidden_states:
732 assert encoder_states is not None
733 encoder_states = encoder_states + (hidden_states,)
734
735 if not return_dict:
736 return tuple(
737 v
738 for v in [hidden_states, encoder_states, all_attentions]
739 if v is not None
740 )
741 return BaseModelOutput(
742 last_hidden_state=hidden_states,
743 hidden_states=encoder_states,
744 attentions=all_attentions,
745 )