| """ |
| FIGMA inference model (inference-only). |
| |
| Loads the packaged FIGMA checkpoint (frozen MuQ audio encoder + frozen E5 text |
| encoder + two Transformer projection heads) and produces the GLOBAL audio/text |
| embeddings used for retrieval. Retrieval score = cosine similarity between the |
| global audio and text embeddings. |
| |
| Note: retrieval uses only the global embeddings (mean-pooled audio frames + E5 |
| [CLS]). The frame/token projection path is used during training only. |
| |
| License: this model bundles MuQ weights (CC BY-NC 4.0), so the composite model is |
| released for NON-COMMERCIAL research use (CC BY-NC 4.0). MuQ and E5 are attributed. |
| """ |
|
|
| import torch |
| import torch.nn as nn |
| import torch.nn.functional as F |
| from torch.nn import TransformerEncoder, TransformerEncoderLayer |
| from transformers import AutoModel, AutoTokenizer |
| from muq import MuQ |
|
|
| TEXT_ENCODER = "intfloat/multilingual-e5-large-instruct" |
| AUDIO_ENCODER = "OpenMuQ/MuQ-large-msd-iter" |
| SAMPLE_RATE = 24000 |
| CLIP_SECONDS = 10.0 |
|
|
|
|
| class ProjectionHead(nn.Module): |
| def __init__(self, input_dim, hidden_dim=512, output_dim=512): |
| super().__init__() |
| layer = TransformerEncoderLayer( |
| d_model=input_dim, nhead=8, dim_feedforward=hidden_dim, |
| dropout=0.1, batch_first=True, |
| ) |
| self.transformer = TransformerEncoder(layer, num_layers=2) |
| self.output_proj = nn.Linear(input_dim, output_dim) |
|
|
| def forward(self, x): |
| if x.dim() == 2: |
| x = x.unsqueeze(1) |
| x = self.transformer(x) |
| x = x.squeeze(1) |
| else: |
| x = self.transformer(x) |
| return self.output_proj(x) |
|
|
|
|
| class Figma(nn.Module): |
| """Submodule names (muq / text_encoder / audio_proj / text_proj) match the |
| training checkpoint, so its state_dict loads directly.""" |
|
|
| def __init__(self, audio_feat_dim=1024): |
| super().__init__() |
| self.muq = MuQ.from_pretrained(AUDIO_ENCODER).eval().requires_grad_(False) |
| self.text_encoder = AutoModel.from_pretrained(TEXT_ENCODER) |
| for p in self.text_encoder.parameters(): |
| p.requires_grad = False |
| self.audio_proj = ProjectionHead(input_dim=audio_feat_dim) |
| self.text_proj = ProjectionHead(input_dim=self.text_encoder.config.hidden_size) |
|
|
| @torch.no_grad() |
| def encode_audio(self, wavs): |
| """wavs: [B, 1, samples] float tensor at 24 kHz -> [B, 512] L2-normalized.""" |
| wavs = torch.nan_to_num(wavs, nan=0.0, posinf=0.0, neginf=0.0) |
| with torch.amp.autocast("cuda", enabled=False): |
| a_seq = self.muq(wavs).last_hidden_state |
| a_clip = a_seq.mean(dim=1) |
| return F.normalize(self.audio_proj(a_clip), dim=-1, eps=1e-8) |
|
|
| @torch.no_grad() |
| def encode_text(self, text_inputs): |
| """text_inputs: dict of tokenized tensors -> [B, 512] L2-normalized.""" |
| t_seq = self.text_encoder(**text_inputs).last_hidden_state |
| t_cls = t_seq[:, 0, :] |
| return F.normalize(self.text_proj(t_cls), dim=-1, eps=1e-8) |
|
|
| @classmethod |
| def from_checkpoint(cls, ckpt_path, device="cpu"): |
| model = cls().to(device).eval() |
| ckpt = torch.load(ckpt_path, map_location="cpu", weights_only=False) |
| sd = ckpt.get("state_dict", ckpt) |
| missing, _ = model.load_state_dict(sd, strict=False) |
| bad = [k for k in missing if k.startswith(("audio_proj", "text_proj"))] |
| if bad: |
| raise RuntimeError(f"Projection-head weights missing from checkpoint: {bad}") |
| return model |
|
|
|
|
| def get_tokenizer(): |
| return AutoTokenizer.from_pretrained(TEXT_ENCODER) |
|
|