import math from pathlib import Path from typing import List, Tuple, Union import numpy as np import torch import torchaudio def get_1d_sincos_pos_embed_from_grid(embed_dim, pos): """NumPy 1D sin-cos positional embedding.""" assert embed_dim % 2 == 0 omega = np.arange(embed_dim // 2, dtype=float) omega /= embed_dim / 2.0 omega = 1.0 / 10000**omega pos = pos.reshape(-1) out = np.einsum("m,d->md", pos, omega) emb_sin = np.sin(out) emb_cos = np.cos(out) return np.concatenate([emb_sin, emb_cos], axis=1) def get_sincos_encoding_1d(pos, dim, freq_scale=25): """Torch 1D sin-cos encoding for band frequency positions.""" device = pos.device n = pos.shape[0] pe = torch.zeros(n, dim, device=device) div_term = torch.exp(torch.arange(0, dim, 2, device=device).float() * (-math.log(10000.0) / dim)) pe[:, 0::2] = torch.sin(pos * freq_scale * div_term) pe[:, 1::2] = torch.cos(pos * freq_scale * div_term) return pe def sanitize_state_dict(state_dict: dict) -> dict: if not isinstance(state_dict, dict) or not state_dict: return state_dict keys = list(state_dict.keys()) if all(k.startswith("module.") for k in keys): return {k[len("module."):]: v for k, v in state_dict.items()} if all(k.startswith("student.") for k in keys): return {k[len("student."):]: v for k, v in state_dict.items()} if all(k.startswith("encoder.") for k in keys): return {k[len("encoder."):]: v for k, v in state_dict.items()} return state_dict def load_checkpoint(path: Union[str, Path]) -> dict: path = Path(path) if not path.exists(): raise FileNotFoundError(f"Checkpoint not found: {path}") if path.suffix == ".safetensors": from safetensors.torch import load_file return load_file(str(path)) payload = torch.load(str(path), map_location="cpu") if isinstance(payload, dict) and "encoder" in payload: return payload["encoder"] return payload def resolve_checkpoint( source: Union[str, Path], filename: str = "model.safetensors", ) -> Path: """Resolve checkpoint path from local file/dir or Hugging Face repo id.""" source_path = Path(source) if source_path.is_file(): return source_path local_candidate = source_path / filename if local_candidate.exists(): return local_candidate try: from huggingface_hub import hf_hub_download except Exception as e: raise ImportError( "huggingface_hub is required when source is a remote repo id. " "Install with `pip install huggingface_hub`." ) from e ckpt_path = hf_hub_download( repo_id=str(source), filename=filename, ) return Path(ckpt_path) def audio_to_spectrogram( audio_signal: torch.Tensor, sample_rate: int, norm_mean: float, norm_std: float, ) -> torch.Tensor: waveform = audio_signal if waveform.dim() == 1: waveform = waveform.unsqueeze(0) waveform = waveform.float() - waveform.float().mean() window_size = int(0.025 * sample_rate) hop_size = int(0.01 * sample_rate) stft = torchaudio.transforms.Spectrogram( n_fft=window_size, hop_length=hop_size, power=1, center=False, ) spec = stft(waveform.squeeze(0)) spec = torch.log(spec + 1e-9) spec = (spec - norm_mean) / (norm_std * 2) return spec def split_segments(spec: torch.Tensor, max_length: int) -> List[torch.Tensor]: segments = [] num_segments = spec.shape[-1] // max_length for i in range(num_segments): segments.append(spec[..., i * max_length : (i + 1) * max_length]) if num_segments * max_length < spec.shape[-1] or not segments: tail = min(spec.shape[-1], max_length) segments.append(spec[..., -tail:]) return segments def aggregate_segment_features( utt_features: List[torch.Tensor], frame_features: List[torch.Tensor], ) -> Tuple[torch.Tensor, torch.Tensor]: utt_feature = torch.stack(utt_features, dim=0).mean(dim=0) frame_feature = torch.vstack(frame_features) return utt_feature, frame_feature