File size: 13,583 Bytes
52a34a3 2bf352b 52a34a3 2bf352b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 | ---
license: cc-by-4.0
library_name: pytorch
pipeline_tag: feature-extraction
tags:
- audio-text-retrieval
- contrastive-learning
- clip
- openclip
- audio-spectrogram-transformer
- cross-modal-alignment
- multimodal-embeddings
base_model: MIT/ast-finetuned-audioset-10-10-0.4593
---
# Edgebind
Audio-into-CLIP alignment for textβimageβaudio retrieval. Edgebind maps audio into the
existing embedding space of a frozen CLIP model instead of training a joint encoder
from scratch, so audio, text and images become directly comparable by cosine
similarity in one 512-d space.
Research artifact for **"Edgebind: Towards Edge-Compatible Audio-into-CLIP Alignment
for Text-Image-Audio Retrieval"** (CAISc 2026).
> **Note on the `datasets` metadata field.** This card intentionally omits it. Clotho
> v2.1 has no canonical publisher-owned dataset repository on the Hub β a search
> returns only third-party mirrors and the unrelated ClothoAQA task. The training code
> downloads Clotho directly from Zenodo, so tagging a mirror would misstate
> provenance. The authoritative source is linked in prose under
> [Training data](#training-data).
## Model description
| Component | Detail |
|---|---|
| Text & image encoders | OpenCLIP ViT-B/32, `hf-hub:laion/CLIP-ViT-B-32-laion2B-s34B-b79K` β **fully frozen**, never updated |
| Audio encoder | Audio Spectrogram Transformer, [`MIT/ast-finetuned-audioset-10-10-0.4593`](https://huggingface.co/MIT/ast-finetuned-audioset-10-10-0.4593) |
| AST frozen | Patch embeddings, position embeddings, encoder layers 0β8 |
| AST trained | Encoder layers 9, 10, 11 and the final layernorm |
| Pooling | Mean of the first two AST output tokens (CLS and distillation) |
| Projection head | `Linear(768, 1024) β LayerNorm(1024) β ReLU β Dropout(0.3) β Linear(1024, 512)` |
| Temperature | Learnable `logit_scale`, initialized to `log(1 / 0.07)` |
| Output | 512-d, L2-normalized, shared with CLIP text and image embeddings |
| Objective | Symmetric InfoNCE (cross-entropy over in-batch negatives in both directions, averaged) |
| Training data | Clotho v2.1, development split |
Only the AST upper layers, the projection head and the temperature receive gradients.
Because the CLIP towers are untouched, text and image embeddings produced by this model
are identical to stock OpenCLIP ViT-B/32 β the alignment is carried entirely by the
audio branch.
The pooling choice is worth stating explicitly, since it is easy to assume otherwise:
audio features are the **average of tokens 0 and 1**, not the CLS token alone. Using
CLS alone is a separate ablation in the training notebook.
## Intended use and limitations
Intended use: text-to-audio and audio-to-text retrieval over a local corpus you
control β embed a set of audio clips once, then rank them against free-text queries by
cosine similarity. Because the CLIP image tower is frozen and shared, images can be
embedded into the same space and searched with the same text queries.
**This is a research prototype, not a production model.** Specifically:
- **Single dataset.** Trained and validated only on Clotho v2.1, which is small
(a few thousand clips) and skewed toward everyday environmental and ambient sound.
Behaviour on speech, music, or domain-specific audio is uncharacterized.
- **Single training run, no seed variance.** One run of 20 epochs. The training code
sets no random seed, and caption sampling, crop offsets and SpecAugment masks are all
stochastic, so run-to-run variance has not been measured. Treat any single reported
number as one sample, not a mean.
- **No CPU, quantized, or on-device benchmarking.** The model was trained and run under
CUDA mixed precision on a single NVIDIA T4. Latency, memory, and accuracy under CPU
inference, quantization, distillation, or mobile/embedded runtimes have **not** been
evaluated. The released checkpoint is unquantized fp32. Despite "edge-compatible" in
the paper title β which refers to the design motivation for reusing a frozen
ViT-B/32 backbone β nothing here establishes that this model is edge-ready or
deployable on constrained hardware. Do not treat it as such.
- **Fixed 10.24 s window.** Audio is cropped or zero-padded to exactly 163,840 samples
at 16 kHz. Longer recordings are truncated, not chunked; content outside the window
is invisible to the model.
- **Inherited bias.** The text and image behaviour is entirely that of
LAION-2B-trained OpenCLIP ViT-B/32 and carries its biases unchanged.
Out of scope: audio captioning or generation (there is no decoder), speaker or speech
recognition, and any safety-, surveillance-, or identity-related classification.
## How to use
The checkpoint is a `state_dict` for the composite module defined in the training
notebook, so you must reconstruct that module β CLIP submodule included β before
loading. The snippet below mirrors the notebook's own model definition and its
evaluation-time preprocessing path.
```python
import numpy as np
import torch
import torch.nn as nn
import torchaudio
import torchaudio.transforms as T
from huggingface_hub import hf_hub_download
from transformers import ASTModel, AutoProcessor
import open_clip
MODEL_NAME = "hf-hub:laion/CLIP-ViT-B-32-laion2B-s34B-b79K"
AST_NAME = "MIT/ast-finetuned-audioset-10-10-0.4593"
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
class OpenCLIP_AST_Model(nn.Module):
def __init__(self, embedding_dim=512):
super().__init__()
self.ast = ASTModel.from_pretrained(AST_NAME)
self.clip_model, _, self.image_preprocess = open_clip.create_model_and_transforms(
MODEL_NAME, pretrained=None
)
self.audio_projection = nn.Sequential(
nn.Linear(self.ast.config.hidden_size, 1024),
nn.LayerNorm(1024),
nn.ReLU(),
nn.Dropout(0.3),
nn.Linear(1024, embedding_dim),
)
self.logit_scale = nn.Parameter(torch.ones([]) * np.log(1 / 0.07))
def forward_audio(self, input_values):
out = self.ast(input_values)
# Mean of CLS + distillation tokens, matching training.
feats = (out.last_hidden_state[:, 0] + out.last_hidden_state[:, 1]) / 2
return self.audio_projection(feats)
weights = hf_hub_download("harryfrz/edgebind", "edgebind_v1.1")
model = OpenCLIP_AST_Model().to(DEVICE)
# The notebook loads with a plain torch.load. On torch >= 2.6 the weights_only=True
# default is appropriate for a pure tensor state_dict; pass weights_only=False if
# your torch version raises on it.
model.load_state_dict(torch.load(weights, map_location=DEVICE))
model.eval()
ast_processor = AutoProcessor.from_pretrained(AST_NAME)
tokenizer = open_clip.get_tokenizer(MODEL_NAME)
SAMPLE_RATE = 16000
TARGET_SAMPLES = 163840 # 10.24 s at 16 kHz
def embed_audio(path):
waveform, sr = torchaudio.load(path)
waveform = waveform.mean(dim=0) if waveform.shape[0] > 1 else waveform.squeeze(0)
if sr != SAMPLE_RATE:
waveform = T.Resample(sr, SAMPLE_RATE)(waveform)
n = waveform.shape[0]
if n < TARGET_SAMPLES:
waveform = torch.nn.functional.pad(waveform, (0, TARGET_SAMPLES - n))
elif n > TARGET_SAMPLES:
start = (n - TARGET_SAMPLES) // 2 # centre crop, as at evaluation time
waveform = waveform[start:start + TARGET_SAMPLES]
inputs = ast_processor(waveform, sampling_rate=SAMPLE_RATE, return_tensors="pt")
with torch.no_grad():
emb = model.forward_audio(inputs["input_values"].to(DEVICE))
return emb / emb.norm(dim=-1, keepdim=True)
def embed_text(prompts):
with torch.no_grad():
emb = model.clip_model.encode_text(tokenizer(prompts).to(DEVICE))
return emb / emb.norm(dim=-1, keepdim=True)
def embed_image(pil_image):
with torch.no_grad():
x = model.image_preprocess(pil_image).unsqueeze(0).to(DEVICE)
emb = model.clip_model.encode_image(x)
return emb / emb.norm(dim=-1, keepdim=True)
# --- retrieval over a local corpus ---
corpus = ["clip_a.wav", "clip_b.wav", "clip_c.wav"]
index = torch.cat([embed_audio(p) for p in corpus], dim=0) # [N, 512]
query = embed_text(["waves hitting the shore"]) # [1, 512]
scores = (query @ index.T)[0] # cosine similarity
for rank in scores.argsort(descending=True):
print(f"{corpus[rank]} {scores[rank].item():.4f}")
```
`open_clip.create_model_and_transforms(MODEL_NAME, pretrained=None)` still downloads
pretrained weights: the `hf-hub:` prefix resolves the checkpoint from the Hub, and
`pretrained=None` only means no additional named tag is applied. Those weights are
then overwritten by the `clip_model.*` entries in the state dict.
### What the checkpoint file contains
`edgebind_v1.1` (955 MB, no file extension). The training code saves with
`torch.save(model.state_dict(), ...)` and reloads with a strict
`model.load_state_dict(torch.load(path, map_location=device))`. That means:
- It is a **raw `state_dict`**, not a training checkpoint β no optimizer state, no
epoch counter, no scheduler or scaler state, no embedded config or metrics.
- It stores the **complete** module, not only the trained tensors. Key prefixes are
`ast.*` (the whole AST, frozen layers 0β8 included), `clip_model.*` (the entire
frozen OpenCLIP ViT-B/32 image *and* text towers), `audio_projection.*`, plus the
scalar `logit_scale`.
- That is roughly 239 M parameters in fp32 (AST β86 M + CLIP β151 M + projection
β1.3 M), consistent with the 955 MB file size.
- Loading is strict, so the module must be rebuilt exactly β including the CLIP
submodule, which is why the snippet above instantiates it.
**This description is derived from the notebook's save/load code and the file size, not
from opening the file.** The tensor keys and dtypes have not been enumerated directly.
If you need that confirmed, load it and inspect `.keys()`.
## Evaluation
**No results table is published in this card.** The training notebook was committed
with all cell outputs stripped, and it contains no code that computes R@1/R@5/R@10,
MedR, MeanR or mAP@10, no CLAP baseline, and no frozen-AST ablation. Transcribing
metrics from any other source would not be verifiable against this repository, so none
are reproduced here.
For the reported text-to-audio and audio-to-text results on the Clotho v2.1 evaluation
split, and the baseline and ablation comparisons, see the CAISc 2026 paper.
The notebook does include a qualitative check β top-3 retrieval for five hardcoded
prompts β and one ablation, CLS-only pooling, described below.
## Training details
### Training data
Clotho v2.1, **development** split, obtained from Zenodo record
[4783391](https://zenodo.org/records/4783391). The training code downloads
`clotho_captions_development.csv` and `clotho_audio_development.7z` from that record
directly. Each audio file carries five human-written captions; one is sampled at random
per example per epoch during training.
### Preprocessing
- Downmix to mono, resample to 16 kHz.
- Crop or zero-pad to 163,840 samples (10.24 s): **random** crop during training,
**centre** crop at evaluation.
- Log-mel features via `AutoProcessor` for `MIT/ast-finetuned-audioset-10-10-0.4593`.
- SpecAugment, training only: `FrequencyMasking(freq_mask_param=24)` and
`TimeMasking(time_mask_param=40)`.
### Hyperparameters
| Setting | Value |
|---|---|
| Optimizer | AdamW, `weight_decay=0.05` |
| Epochs | 20 |
| Batch size | 64 (gradient accumulation steps = 1, so effective batch = 64) |
| LR β AST layers 9β11 | 5e-6 |
| LR β AST final layernorm | 5e-6 |
| LR β projection head | 2e-4 |
| LR β logit scale | 2e-4 |
| Schedule | `CosineAnnealingLR`, `eta_min=1e-6`, stepped per optimizer step |
| Precision | CUDA mixed precision (`torch.amp.autocast` + `GradScaler`) |
| Loader | `drop_last=True`, `num_workers=4` |
### Compute
Single NVIDIA T4. The training cell's recorded execution window spans **50 min 25 s**
for all 20 epochs plus audio-embedding export. That figure comes from notebook cell
execution timestamps, not from a printed training log.
### Ablation included in the code
**CLS-only pooling** β identical in every other respect, but uses
`last_hidden_state[:, 0]` instead of the mean of tokens 0 and 1. It trains to a
separate checkpoint. No metrics for it are present in the notebook.
## A note on naming
Some artifacts and in-code comments use the internal name **"Sage-Embed"** (for
example, `# Verified Sage-Embed v1.1 train/freeze configuration`). This refers to the
same model as Edgebind. The `v1.1` suffix on the checkpoint filename corresponds to
that internal versioning.
## Links
- **Code:** https://github.com/harryfrzz/edgebind
- **Paper:** "Edgebind: Towards Edge-Compatible Audio-into-CLIP Alignment for
Text-Image-Audio Retrieval", CAISc 2026
- **Training data:** Clotho v2.1 β https://zenodo.org/records/4783391
- **Base audio model:** https://huggingface.co/MIT/ast-finetuned-audioset-10-10-0.4593
- **Base CLIP model:** https://huggingface.co/laion/CLIP-ViT-B-32-laion2B-s34B-b79K
## Citation
```bibtex
@inproceedings{edgebind2026,
title = {Edgebind: Towards Edge-Compatible Audio-into-CLIP Alignment for
Text-Image-Audio Retrieval},
author = {Harikrishna C},
booktitle = {CAISc},
year = {2026}
}
```
The repository records no full author list, DOI, or page numbers; the author field
above is taken from commit metadata and should be completed before use.
## License
CC BY 4.0.
|