FIGMA / figma_model.py
nishitanand's picture
Upload folder using huggingface_hub
18b215f verified
Raw
History Blame Contribute Delete
3.58 kB
"""
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)