Xunzhuo commited on
Commit
10711a0
·
verified ·
1 Parent(s): 82f31e2

Improve multimodal embeddings

Browse files
Files changed (6) hide show
  1. README.md +21 -21
  2. config.json +5 -3
  3. model.safetensors +2 -2
  4. omni_components/residual_projection.py +34 -0
  5. scores.json +239 -127
  6. vela_omni.py +23 -4
README.md CHANGED
@@ -22,7 +22,7 @@ tags:
22
 
23
  # Vela Omni Nano
24
 
25
- Vela Omni Nano maps text, images, and speech into a shared embedding space for multimodal search and matching.
26
 
27
  **134M parameters · 384 dimensions · L2-normalized embeddings.**
28
 
@@ -34,26 +34,26 @@ Scores use a 0–100 scale; higher is better. All applicable models use the same
34
 
35
  | Metric | [Vela-1.0-Encoder-307M-Embedding](https://huggingface.co/llm-semantic-router/Vela-1.0-Encoder-307M-Embedding) | [multi-modal-embed-small](https://huggingface.co/llm-semantic-router/multi-modal-embed-small) | [multi-modal-embed-large](https://huggingface.co/llm-semantic-router/multi-modal-embed-large) | [Vela-1.0-Omni-Nano](https://huggingface.co/llm-semantic-router/Vela-1.0-Omni-Nano) |
36
  |---|---:|---:|---:|---:|
37
- | Banking77 · Accuracy | 80.00 | 70.42 | 75.78 | 70.42 |
38
- | MASSIVE English · Accuracy | 75.64 | 65.95 | 72.31 | 65.95 |
39
- | COCO · Image → text · R@1 | N/A | 40.83 | 42.53 | 44.23 |
40
- | COCO · Image → text · R@5 | N/A | 67.19 | 75.21 | 73.03 |
41
- | COCO · Image → text · R@10 | N/A | 78.49 | 87.61 | 86.03 |
42
- | COCO · Text → image · R@1 | N/A | 30.18 | 35.04 | 36.11 |
43
- | COCO · Text → image · R@5 | N/A | 59.42 | 70.09 | 68.72 |
44
- | COCO · Text → image · R@10 | N/A | 74.29 | 83.91 | 82.02 |
45
- | LibriSpeech · Audio → text · R@1 | N/A | 4.21 | 56.99 | 5.29 |
46
- | LibriSpeech · Audio → text · R@5 | N/A | 11.99 | 81.85 | 15.82 |
47
- | LibriSpeech · Audio → text · R@10 | N/A | 19.03 | 87.94 | 23.90 |
48
- | LibriSpeech · Text → audio · R@1 | N/A | 9.58 | 78.58 | 11.61 |
49
- | LibriSpeech · Text → audio · R@5 | N/A | 22.53 | 94.02 | 26.55 |
50
- | LibriSpeech · Text → audio · R@10 | N/A | 30.69 | 97.01 | 35.44 |
51
-
52
- Text evaluation uses fixed class prototypes: 3,080 Banking77 and 2,972 MASSIVE English queries. This is nearest-prototype classification, not the MTEB classification protocol.
53
 
54
  Image retrieval uses the COCO Karpathy CC-BY2 image subset: 823 images and 4,115 captions. Speech retrieval uses 2,611 LibriSpeech test-clean clips and 2,610 unique transcripts, with clips limited to 30 seconds. Recall is measured against each complete candidate pool, accepting all matching positives.
55
 
56
- Evaluation uses FP32 embeddings, cosine similarity, and a common 128-token text limit. [Full scores and protocol](./scores.json) include model revisions, sample counts, and paired uncertainty.
57
 
58
  ## Quick start
59
 
@@ -72,10 +72,10 @@ vectors = model.encode_text(["A bicycle beside a building.", "Someone is reading
72
  print(vectors.shape) # (2, 384)
73
  ```
74
 
75
- Pass a list of Pillow images to `model.encode_image(images)`. Pass a list of mono NumPy waveforms to `model.encode_audio(waveforms, sampling_rate=16000)`; each waveform must be at most 30 seconds. Compare normalized vectors with their dot product. Similarity scores are rankings, not calibrated probabilities.
76
 
77
- Cross-modal alignment uses COCO image–caption pairs from the CC-BY 2.0 image subset and [LibriSpeech](https://www.openslr.org/12/) speech–transcript pairs (CC-BY 4.0). COCO annotations are provided under CC-BY 4.0.
78
 
79
  The repository includes the native model code, component configurations, and tokenizer and processor files. See [NOTICE](./NOTICE) and [LICENSE](./LICENSE) for component attribution and license terms.
80
 
81
- [Explore the Vela model collection](https://huggingface.co/collections/llm-semantic-router/vela-10-router-models-6aa555ba70cc6997d6d67798)
 
22
 
23
  # Vela Omni Nano
24
 
25
+ Vela Omni Nano maps text, images, and speech into a shared embedding space for multimodal search, routing, and clustering.
26
 
27
  **134M parameters · 384 dimensions · L2-normalized embeddings.**
28
 
 
34
 
35
  | Metric | [Vela-1.0-Encoder-307M-Embedding](https://huggingface.co/llm-semantic-router/Vela-1.0-Encoder-307M-Embedding) | [multi-modal-embed-small](https://huggingface.co/llm-semantic-router/multi-modal-embed-small) | [multi-modal-embed-large](https://huggingface.co/llm-semantic-router/multi-modal-embed-large) | [Vela-1.0-Omni-Nano](https://huggingface.co/llm-semantic-router/Vela-1.0-Omni-Nano) |
36
  |---|---:|---:|---:|---:|
37
+ | Banking77 · Accuracy | 80.00 | 70.42 | 75.78 | 73.28 |
38
+ | MASSIVE English · Accuracy | 75.64 | 65.95 | 72.31 | 66.55 |
39
+ | COCO · Image → text · R@1 | N/A | 40.83 | 42.53 | 47.02 |
40
+ | COCO · Image → text · R@5 | N/A | 67.19 | 75.21 | 74.97 |
41
+ | COCO · Image → text · R@10 | N/A | 78.49 | 87.61 | 85.91 |
42
+ | COCO · Text → image · R@1 | N/A | 30.18 | 35.04 | 37.33 |
43
+ | COCO · Text → image · R@5 | N/A | 59.42 | 70.09 | 70.16 |
44
+ | COCO · Text → image · R@10 | N/A | 74.29 | 83.91 | 82.99 |
45
+ | LibriSpeech · Audio → text · R@1 | N/A | 4.21 | 56.99 | 8.20 |
46
+ | LibriSpeech · Audio → text · R@5 | N/A | 11.99 | 81.85 | 21.29 |
47
+ | LibriSpeech · Audio → text · R@10 | N/A | 19.03 | 87.94 | 30.26 |
48
+ | LibriSpeech · Text → audio · R@1 | N/A | 9.58 | 78.58 | 13.26 |
49
+ | LibriSpeech · Text → audio · R@5 | N/A | 22.53 | 94.02 | 30.00 |
50
+ | LibriSpeech · Text → audio · R@10 | N/A | 30.69 | 97.01 | 38.66 |
51
+
52
+ Text evaluation uses fixed class prototypes: 3,080 Banking77 and 2,972 MASSIVE English queries. Vela Omni is adapted using training examples and intent labels from these two datasets; comparison models are evaluated as published without additional adaptation here. These scores measure supervised task adaptation with nearest-prototype classification, not zero-shot transfer or the MTEB classification protocol.
53
 
54
  Image retrieval uses the COCO Karpathy CC-BY2 image subset: 823 images and 4,115 captions. Speech retrieval uses 2,611 LibriSpeech test-clean clips and 2,610 unique transcripts, with clips limited to 30 seconds. Recall is measured against each complete candidate pool, accepting all matching positives.
55
 
56
+ Evaluation uses FP32 embeddings, cosine similarity, and a common 128-token text limit. [Full scores and protocol](./scores.json) include model revisions, sample counts, and paired uncertainty against the original and previous models. These fixed evaluation pools are reused across releases.
57
 
58
  ## Quick start
59
 
 
72
  print(vectors.shape) # (2, 384)
73
  ```
74
 
75
+ Pass a list of Pillow images to `model.encode_image(images)`. Pass a list of mono NumPy waveforms to `model.encode_audio(waveforms, sampling_rate=16000)`; each waveform must be at most 30 seconds. Compare normalized vectors with their dot product. To route media, embed each destination’s name and description, then select the closest vector. To discover routing categories, cluster media embeddings and inspect each group. Similarity scores are rankings, not calibrated probabilities.
76
 
77
+ Cross-modal alignment uses COCO image–caption pairs from the CC-BY 2.0 image subset and [LibriSpeech](https://www.openslr.org/12/) speech–transcript pairs (CC-BY 4.0). COCO annotations, Banking77, and MASSIVE are provided under CC-BY 4.0.
78
 
79
  The repository includes the native model code, component configurations, and tokenizer and processor files. See [NOTICE](./NOTICE) and [LICENSE](./LICENSE) for component attribution and license terms.
80
 
81
+ [Explore the Vela model collection](https://huggingface.co/collections/llm-semantic-router/vela-10-6aa555ba70cc6997d6d67798)
config.json CHANGED
@@ -1,7 +1,7 @@
1
  {
2
- "format_version": 2,
3
  "variant": "nano",
4
- "parameter_count": 133907328,
5
  "embedding_dim": 384,
6
  "max_text_length": 128,
7
  "architectures": [
@@ -11,5 +11,7 @@
11
  "library_name": "pytorch",
12
  "audio_sampling_rate": 16000,
13
  "audio_max_seconds": 30,
14
- "audio_projection": "linear"
 
 
15
  }
 
1
  {
2
+ "format_version": 3,
3
  "variant": "nano",
4
+ "parameter_count": 133956480,
5
  "embedding_dim": 384,
6
  "max_text_length": 128,
7
  "architectures": [
 
11
  "library_name": "pytorch",
12
  "audio_sampling_rate": 16000,
13
  "audio_max_seconds": 30,
14
+ "audio_projection": "linear",
15
+ "text_projection": "residual_rank64",
16
+ "audio_pooling": "mean"
17
  }
model.safetensors CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:aa28600e25bdfb3796fc173fd9504c5a4687c07211d7e3dbb751dceef1a8c341
3
- size 535689632
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:949b21264db9d963de79a56dd2aeb4f1f31dce315dc54ee15296b7c69772e7b9
3
+ size 535886448
omni_components/residual_projection.py ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Native rank-64 residual text mapping and duration-aware audio readout."""
2
+ import torch
3
+ from torch import nn
4
+
5
+
6
+ class ResidualTextProjection(nn.Module):
7
+ def __init__(self, dimension, seed=20260919):
8
+ super().__init__()
9
+ self.V = nn.Linear(dimension, 64, bias=False)
10
+ self.U = nn.Linear(64, dimension, bias=False)
11
+ generator = torch.Generator(device='cpu').manual_seed(seed)
12
+ with torch.no_grad():
13
+ self.V.weight.copy_(torch.randn(64, dimension, generator=generator) * 0.01)
14
+ self.U.weight.zero_()
15
+
16
+ def forward(self, values):
17
+ return values + self.U(self.V(values))
18
+
19
+
20
+ def duration_frame_counts(sample_counts, frame_count, device=None):
21
+ counts = torch.as_tensor(sample_counts, device=device)
22
+ if counts.ndim != 1 or counts.dtype not in (torch.int32, torch.int64):
23
+ raise ValueError('Original integer sample counts are required')
24
+ if not bool(((counts > 0) & (counts <= 480000)).all()) or frame_count != 1500:
25
+ raise ValueError('Expected valid 16kHz clips and native 1500-frame output')
26
+ return torch.clamp(torch.div(counts + 319, 320, rounding_mode='floor'), 1, frame_count)
27
+
28
+
29
+ def duration_mean(hidden_states, sample_counts):
30
+ if hidden_states.ndim != 3 or len(sample_counts) != len(hidden_states):
31
+ raise ValueError('Audio lengths do not match encoder batch')
32
+ counts = duration_frame_counts(sample_counts, hidden_states.shape[1], hidden_states.device)
33
+ mask = torch.arange(hidden_states.shape[1], device=hidden_states.device)[None, :] < counts[:, None]
34
+ return (hidden_states * mask[:, :, None]).sum(dim=1) / counts[:, None].to(hidden_states.dtype)
scores.json CHANGED
@@ -1,5 +1,5 @@
1
  {
2
- "schema_version": 1,
3
  "model": "llm-semantic-router/Vela-1.0-Omni-Nano",
4
  "revision_semantics": "self denotes the Hugging Face snapshot containing this file; the native artifact fingerprint binds its evaluated model files.",
5
  "score_scale": "0-1; multiply by 100 for percentages",
@@ -182,77 +182,77 @@
182
  "candidate": {
183
  "model": "llm-semantic-router/Vela-1.0-Omni-Nano",
184
  "revision": "self",
185
- "artifact_fingerprint_sha256": "32f410c3febdbd02e3f790b907db482e4ae1d493e6a1c4eaab375ebada234f8b",
186
  "metrics": {
187
  "text.banking77.accuracy": {
188
- "numerator": 2169,
189
  "denominator": 3080,
190
- "value": 0.7042207792207792
191
  },
192
  "text.massive-en.accuracy": {
193
- "numerator": 1960,
194
  "denominator": 2972,
195
- "value": 0.6594885598923284
196
  },
197
  "image.image_to_text.recall@1": {
198
- "numerator": 364,
199
  "denominator": 823,
200
- "value": 0.4422843256379101
201
  },
202
  "image.image_to_text.recall@5": {
203
- "numerator": 601,
204
  "denominator": 823,
205
- "value": 0.7302551640340219
206
  },
207
  "image.image_to_text.recall@10": {
208
- "numerator": 708,
209
  "denominator": 823,
210
- "value": 0.8602673147023087
211
  },
212
  "image.text_to_image.recall@1": {
213
- "numerator": 1486,
214
  "denominator": 4115,
215
- "value": 0.3611178614823815
216
  },
217
  "image.text_to_image.recall@5": {
218
- "numerator": 2828,
219
  "denominator": 4115,
220
- "value": 0.6872417982989064
221
  },
222
  "image.text_to_image.recall@10": {
223
- "numerator": 3375,
224
  "denominator": 4115,
225
- "value": 0.8201701093560145
226
  },
227
  "audio.audio_to_text.recall@1": {
228
- "numerator": 138,
229
  "denominator": 2611,
230
- "value": 0.05285331290693221
231
  },
232
  "audio.audio_to_text.recall@5": {
233
- "numerator": 413,
234
  "denominator": 2611,
235
- "value": 0.1581769436997319
236
  },
237
  "audio.audio_to_text.recall@10": {
238
- "numerator": 624,
239
  "denominator": 2611,
240
- "value": 0.23898889314438912
241
  },
242
  "audio.text_to_audio.recall@1": {
243
- "numerator": 303,
244
  "denominator": 2610,
245
- "value": 0.11609195402298851
246
  },
247
  "audio.text_to_audio.recall@5": {
248
- "numerator": 693,
249
  "denominator": 2610,
250
- "value": 0.2655172413793103
251
  },
252
  "audio.text_to_audio.recall@10": {
253
- "numerator": 925,
254
  "denominator": 2610,
255
- "value": 0.3544061302681992
256
  }
257
  }
258
  }
@@ -312,109 +312,221 @@
312
  "similarity": "FP32 cosine of L2-normalized embeddings",
313
  "ties": "lexicographic candidate ID",
314
  "retrieval": "complete candidate pools; all annotated positives",
315
- "classification": "nearest normalized mean class prototype; fixed prototype split",
316
  "aggregation": "Each family is the mean of its two primary metrics; utility is the mean of all six primaries. Retrieval primaries are R@1; R@5 and R@10 are reported separately.",
317
- "evaluation_scope": "fixed test-split evaluation pools"
318
  },
319
  "paired_uncertainty": {
320
- "comparison": {
321
- "candidate": "llm-semantic-router/Vela-1.0-Omni-Nano",
322
- "baseline": "llm-semantic-router/multi-modal-embed-small"
323
- },
324
- "seed": 20260918,
325
- "replicates": 2000,
326
- "method": "paired stratified group percentile; PCG64; fixed candidate pools",
327
- "intervals": {
328
- "audio.audio_to_text.recall@1": {
329
- "point_delta": 0.010723860589812333,
330
- "lower": 0.005401297417205939,
331
- "upper": 0.016930329895273797
332
- },
333
- "audio.audio_to_text.recall@10": {
334
- "point_delta": 0.048640367675220224,
335
- "lower": 0.03862647576747038,
336
- "upper": 0.057686079679882395
337
- },
338
- "audio.audio_to_text.recall@5": {
339
- "point_delta": 0.038299502106472615,
340
- "lower": 0.028966882934863172,
341
- "upper": 0.04718931610578753
342
- },
343
- "audio.text_to_audio.recall@1": {
344
- "point_delta": 0.020306513409961684,
345
- "lower": 0.01207735759631001,
346
- "upper": 0.029131333167300467
347
- },
348
- "audio.text_to_audio.recall@10": {
349
- "point_delta": 0.0475095785440613,
350
- "lower": 0.036695309471507946,
351
- "upper": 0.05838249564916254
352
- },
353
- "audio.text_to_audio.recall@5": {
354
- "point_delta": 0.040229885057471264,
355
- "lower": 0.027551627678505008,
356
- "upper": 0.05248271039052227
357
- },
358
- "family.audio": {
359
- "point_delta": 0.015515186999887009,
360
- "lower": 0.010126625706639377,
361
- "upper": 0.021365285246569197
362
  },
363
- "family.image": {
364
- "point_delta": 0.04665856622114216,
365
- "lower": 0.031831713244228434,
366
- "upper": 0.061849939246658554
367
- },
368
- "family.text": {
369
- "point_delta": 0.0,
370
- "lower": 0.0,
371
- "upper": 0.0
372
- },
373
- "image.image_to_text.recall@1": {
374
- "point_delta": 0.03402187120291616,
375
- "lower": 0.009720534629404616,
376
- "upper": 0.05953827460510328
377
- },
378
- "image.image_to_text.recall@10": {
379
- "point_delta": 0.07533414337788578,
380
- "lower": 0.05103280680437424,
381
- "upper": 0.10085054678007291
382
- },
383
- "image.image_to_text.recall@5": {
384
- "point_delta": 0.0583232077764277,
385
- "lower": 0.031591737545565005,
386
- "upper": 0.08383961117861483
387
- },
388
- "image.text_to_image.recall@1": {
389
- "point_delta": 0.059295261239368166,
390
- "lower": 0.04325637910085055,
391
- "upper": 0.07630619684082625
392
- },
393
- "image.text_to_image.recall@10": {
394
- "point_delta": 0.07727825030376671,
395
- "lower": 0.061482381530984204,
396
- "upper": 0.0928311057108141
397
- },
398
- "image.text_to_image.recall@5": {
399
- "point_delta": 0.09307411907654921,
400
- "lower": 0.07606318347509113,
401
- "upper": 0.11009113001215064
402
- },
403
- "text.banking77.accuracy": {
404
- "point_delta": 0.0,
405
- "lower": 0.0,
406
- "upper": 0.0
407
- },
408
- "text.massive-en.accuracy": {
409
- "point_delta": 0.0,
410
- "lower": 0.0,
411
- "upper": 0.0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
412
  },
413
- "utility": {
414
- "point_delta": 0.020724584407009722,
415
- "lower": 0.015571272805177982,
416
- "upper": 0.026256156724631264
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
417
  }
418
  }
 
 
 
 
 
419
  }
420
  }
 
1
  {
2
+ "schema_version": 2,
3
  "model": "llm-semantic-router/Vela-1.0-Omni-Nano",
4
  "revision_semantics": "self denotes the Hugging Face snapshot containing this file; the native artifact fingerprint binds its evaluated model files.",
5
  "score_scale": "0-1; multiply by 100 for percentages",
 
182
  "candidate": {
183
  "model": "llm-semantic-router/Vela-1.0-Omni-Nano",
184
  "revision": "self",
185
+ "artifact_fingerprint_sha256": "e4c8165db4e76534f71adbe24719d464b3d416ee9e7fe49c52a5aa05b3045371",
186
  "metrics": {
187
  "text.banking77.accuracy": {
188
+ "numerator": 2257,
189
  "denominator": 3080,
190
+ "value": 0.7327922077922078
191
  },
192
  "text.massive-en.accuracy": {
193
+ "numerator": 1978,
194
  "denominator": 2972,
195
+ "value": 0.6655450874831763
196
  },
197
  "image.image_to_text.recall@1": {
198
+ "numerator": 387,
199
  "denominator": 823,
200
+ "value": 0.47023086269744835
201
  },
202
  "image.image_to_text.recall@5": {
203
+ "numerator": 617,
204
  "denominator": 823,
205
+ "value": 0.7496962332928311
206
  },
207
  "image.image_to_text.recall@10": {
208
+ "numerator": 707,
209
  "denominator": 823,
210
+ "value": 0.8590522478736331
211
  },
212
  "image.text_to_image.recall@1": {
213
+ "numerator": 1536,
214
  "denominator": 4115,
215
+ "value": 0.3732685297691373
216
  },
217
  "image.text_to_image.recall@5": {
218
+ "numerator": 2887,
219
  "denominator": 4115,
220
+ "value": 0.7015795868772783
221
  },
222
  "image.text_to_image.recall@10": {
223
+ "numerator": 3415,
224
  "denominator": 4115,
225
+ "value": 0.8298906439854192
226
  },
227
  "audio.audio_to_text.recall@1": {
228
+ "numerator": 214,
229
  "denominator": 2611,
230
+ "value": 0.0819609345078514
231
  },
232
  "audio.audio_to_text.recall@5": {
233
+ "numerator": 556,
234
  "denominator": 2611,
235
+ "value": 0.21294523171198773
236
  },
237
  "audio.audio_to_text.recall@10": {
238
+ "numerator": 790,
239
  "denominator": 2611,
240
+ "value": 0.30256606664113367
241
  },
242
  "audio.text_to_audio.recall@1": {
243
+ "numerator": 346,
244
  "denominator": 2610,
245
+ "value": 0.13256704980842912
246
  },
247
  "audio.text_to_audio.recall@5": {
248
+ "numerator": 783,
249
  "denominator": 2610,
250
+ "value": 0.3
251
  },
252
  "audio.text_to_audio.recall@10": {
253
+ "numerator": 1009,
254
  "denominator": 2610,
255
+ "value": 0.38659003831417627
256
  }
257
  }
258
  }
 
312
  "similarity": "FP32 cosine of L2-normalized embeddings",
313
  "ties": "lexicographic candidate ID",
314
  "retrieval": "complete candidate pools; all annotated positives",
315
+ "classification": "nearest normalized mean class prototype; Vela Omni uses supervised training-split intent-label adaptation; comparison models evaluated as published",
316
  "aggregation": "Each family is the mean of its two primary metrics; utility is the mean of all six primaries. Retrieval primaries are R@1; R@5 and R@10 are reported separately.",
317
+ "evaluation_scope": "fixed test-split evaluation pools reused across releases"
318
  },
319
  "paired_uncertainty": {
320
+ "original": {
321
+ "comparison": {
322
+ "candidate": "llm-semantic-router/Vela-1.0-Omni-Nano",
323
+ "baseline": "llm-semantic-router/multi-modal-embed-small",
324
+ "baseline_revision": "fdf8e01b7b0f3a69ac1ac8e2a64dcb1ede177ba4",
325
+ "baseline_artifact_fingerprint_sha256": "684fe0c48a4ddd3944167f6e5dc75e97ada24e5b120bd9a2921f00dc03112eeb"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
326
  },
327
+ "seed": 20260918,
328
+ "replicates": 2000,
329
+ "method": "paired stratified group percentile; PCG64; fixed candidate pools",
330
+ "intervals": {
331
+ "audio.audio_to_text.recall@1": {
332
+ "point_delta": 0.03983148219073152,
333
+ "lower": 0.0325962321534125,
334
+ "upper": 0.047758993112907225
335
+ },
336
+ "audio.audio_to_text.recall@10": {
337
+ "point_delta": 0.11221754117196477,
338
+ "lower": 0.09530973319687737,
339
+ "upper": 0.12877810022670813
340
+ },
341
+ "audio.audio_to_text.recall@5": {
342
+ "point_delta": 0.09306779011872846,
343
+ "lower": 0.07882957120502015,
344
+ "upper": 0.10728804617676266
345
+ },
346
+ "audio.text_to_audio.recall@1": {
347
+ "point_delta": 0.0367816091954023,
348
+ "lower": 0.026758054192447193,
349
+ "upper": 0.04703357251126449
350
+ },
351
+ "audio.text_to_audio.recall@10": {
352
+ "point_delta": 0.07969348659003832,
353
+ "lower": 0.06278676122484814,
354
+ "upper": 0.09535034581998793
355
+ },
356
+ "audio.text_to_audio.recall@5": {
357
+ "point_delta": 0.07471264367816093,
358
+ "lower": 0.058304011796927804,
359
+ "upper": 0.08890972870306472
360
+ },
361
+ "family.audio": {
362
+ "point_delta": 0.03830654569306691,
363
+ "lower": 0.0317536163235943,
364
+ "upper": 0.04491423341337552
365
+ },
366
+ "family.image": {
367
+ "point_delta": 0.06670716889428918,
368
+ "lower": 0.04933171324422843,
369
+ "upper": 0.08457168894289184
370
+ },
371
+ "family.text": {
372
+ "point_delta": 0.017313978081138243,
373
+ "lower": 0.012190932632665139,
374
+ "upper": 0.022698066843763527
375
+ },
376
+ "image.image_to_text.recall@1": {
377
+ "point_delta": 0.061968408262454436,
378
+ "lower": 0.03402187120291616,
379
+ "upper": 0.09234507897934387
380
+ },
381
+ "image.image_to_text.recall@10": {
382
+ "point_delta": 0.07411907654921021,
383
+ "lower": 0.04738760631834751,
384
+ "upper": 0.10085054678007291
385
+ },
386
+ "image.image_to_text.recall@5": {
387
+ "point_delta": 0.07776427703523693,
388
+ "lower": 0.04981773997569866,
389
+ "upper": 0.10814094775212636
390
+ },
391
+ "image.text_to_image.recall@1": {
392
+ "point_delta": 0.07144592952612394,
393
+ "lower": 0.05297691373025516,
394
+ "upper": 0.09015795868772783
395
+ },
396
+ "image.text_to_image.recall@10": {
397
+ "point_delta": 0.08699878493317133,
398
+ "lower": 0.07047387606318348,
399
+ "upper": 0.10352369380315918
400
+ },
401
+ "image.text_to_image.recall@5": {
402
+ "point_delta": 0.10741190765492102,
403
+ "lower": 0.08894289185905224,
404
+ "upper": 0.12613001215066824
405
+ },
406
+ "text.banking77.accuracy": {
407
+ "point_delta": 0.02857142857142857,
408
+ "lower": 0.020461188697629102,
409
+ "upper": 0.03666480107400848
410
+ },
411
+ "text.massive-en.accuracy": {
412
+ "point_delta": 0.006056527590847914,
413
+ "lower": -0.00033658984096547205,
414
+ "upper": 0.013131423701480958
415
+ },
416
+ "utility": {
417
+ "point_delta": 0.04077589755616478,
418
+ "lower": 0.034528310785056776,
419
+ "upper": 0.047151560938567774
420
+ }
421
+ }
422
+ },
423
+ "previous": {
424
+ "comparison": {
425
+ "candidate": "llm-semantic-router/Vela-1.0-Omni-Nano",
426
+ "baseline": "llm-semantic-router/Vela-1.0-Omni-Nano",
427
+ "baseline_revision": "82f31e22b31f01989686559d974159034fa8118c",
428
+ "baseline_artifact_fingerprint_sha256": "32f410c3febdbd02e3f790b907db482e4ae1d493e6a1c4eaab375ebada234f8b"
429
  },
430
+ "seed": 20260918,
431
+ "replicates": 2000,
432
+ "method": "paired stratified group percentile; PCG64; fixed candidate pools",
433
+ "intervals": {
434
+ "audio.audio_to_text.recall@1": {
435
+ "point_delta": 0.02910762160091919,
436
+ "lower": 0.021861939983398526,
437
+ "upper": 0.036651355202609114
438
+ },
439
+ "audio.audio_to_text.recall@10": {
440
+ "point_delta": 0.06357717349674455,
441
+ "lower": 0.05113845782378832,
442
+ "upper": 0.0757709413136462
443
+ },
444
+ "audio.audio_to_text.recall@5": {
445
+ "point_delta": 0.05476828801225584,
446
+ "lower": 0.04306058450795293,
447
+ "upper": 0.06643108320584735
448
+ },
449
+ "audio.text_to_audio.recall@1": {
450
+ "point_delta": 0.016475095785440614,
451
+ "lower": 0.008695160152854542,
452
+ "upper": 0.023524944652131282
453
+ },
454
+ "audio.text_to_audio.recall@10": {
455
+ "point_delta": 0.03218390804597701,
456
+ "lower": 0.02000716134837118,
457
+ "upper": 0.04482972738447
458
+ },
459
+ "audio.text_to_audio.recall@5": {
460
+ "point_delta": 0.034482758620689655,
461
+ "lower": 0.02224784413518575,
462
+ "upper": 0.04650418024307072
463
+ },
464
+ "family.audio": {
465
+ "point_delta": 0.0227913586931799,
466
+ "lower": 0.017447187890587527,
467
+ "upper": 0.028299187706426518
468
+ },
469
+ "family.image": {
470
+ "point_delta": 0.020048602673147023,
471
+ "lower": 0.009477521263669502,
472
+ "upper": 0.03062272174969622
473
+ },
474
+ "family.text": {
475
+ "point_delta": 0.017313978081138243,
476
+ "lower": 0.012190932632665139,
477
+ "upper": 0.022698066843763527
478
+ },
479
+ "image.image_to_text.recall@1": {
480
+ "point_delta": 0.027946537059538274,
481
+ "lower": 0.00850546780072904,
482
+ "upper": 0.04738760631834751
483
+ },
484
+ "image.image_to_text.recall@10": {
485
+ "point_delta": -0.001215066828675577,
486
+ "lower": -0.013365735115431349,
487
+ "upper": 0.00975091130012134
488
+ },
489
+ "image.image_to_text.recall@5": {
490
+ "point_delta": 0.019441069258809233,
491
+ "lower": 0.002430133657351154,
492
+ "upper": 0.03766707168894289
493
+ },
494
+ "image.text_to_image.recall@1": {
495
+ "point_delta": 0.012150668286755772,
496
+ "lower": 0.003639125151883354,
497
+ "upper": 0.020656136087484813
498
+ },
499
+ "image.text_to_image.recall@10": {
500
+ "point_delta": 0.009720534629404616,
501
+ "lower": 0.003888213851761847,
502
+ "upper": 0.015795868772782502
503
+ },
504
+ "image.text_to_image.recall@5": {
505
+ "point_delta": 0.014337788578371811,
506
+ "lower": 0.006804374240583232,
507
+ "upper": 0.02235722964763062
508
+ },
509
+ "text.banking77.accuracy": {
510
+ "point_delta": 0.02857142857142857,
511
+ "lower": 0.020461188697629102,
512
+ "upper": 0.03666480107400848
513
+ },
514
+ "text.massive-en.accuracy": {
515
+ "point_delta": 0.006056527590847914,
516
+ "lower": -0.00033658984096547205,
517
+ "upper": 0.013131423701480958
518
+ },
519
+ "utility": {
520
+ "point_delta": 0.020051313149155054,
521
+ "lower": 0.01592880942853571,
522
+ "upper": 0.024331405417179264
523
+ }
524
  }
525
  }
526
+ },
527
+ "training_scope": {
528
+ "text": "Banking77 and MASSIVE English training examples and intent labels; task-adapted classification, not zero-shot transfer or standard MTEB classification",
529
+ "image": "COCO CC-BY2 training image-caption subset",
530
+ "audio": "LibriSpeech train-clean-100 speech-transcript subset"
531
  }
532
  }
vela_omni.py CHANGED
@@ -8,19 +8,27 @@ from safetensors.torch import load_file
8
  from torch import nn
9
  from torch.nn import functional as F
10
 
 
11
  from omni_components.embedder import MultimodalEmbedder
12
  from omni_components.mini import MultiModalSentenceEmbedder
13
 
14
 
15
  class VelaOmni(nn.Module):
16
- def __init__(self, root, variant, legacy=False, audio_projection="identity"):
17
  super().__init__()
18
  self.variant = variant
 
 
 
 
 
19
  self.embedding_dim = 384 if variant == "nano" else 768
20
  self.max_text_length = 128 if variant == "nano" else 32768
21
  if variant == "nano":
22
  self.model = MultimodalEmbedder(Path(root) / "components", legacy=legacy)
23
  self.tokenizer = self.model.text_encoder.tokenizer
 
 
24
  if audio_projection == "linear":
25
  self.model.audio_encoder.projection = nn.Linear(384, 384)
26
  elif audio_projection != "identity":
@@ -30,6 +38,8 @@ class VelaOmni(nn.Module):
30
  raise ValueError("Mini uses its native audio projection")
31
  self.model = MultiModalSentenceEmbedder(Path(root) / "components", legacy=legacy)
32
  self.tokenizer = self.model.text_model.tokenizer
 
 
33
  else:
34
  raise ValueError("Unknown Vela Omni variant")
35
 
@@ -41,9 +51,11 @@ class VelaOmni(nn.Module):
41
  root = Path(snapshot_download(repo_id, revision=revision,
42
  allow_patterns=["config.json", "model.safetensors", "components/*"]))
43
  config = json.loads((root / "config.json").read_text())
44
- if config.get("format_version") not in (1, 2):
45
  raise ValueError("Unsupported Vela Omni artifact format")
46
- instance = cls(root, config["variant"], audio_projection=config.get("audio_projection", "identity"))
 
 
47
  weights = load_file(str(root / "model.safetensors"), device="cpu")
48
  result = instance.model.load_state_dict(weights, strict=True, assign=True)
49
  if result.missing_keys or result.unexpected_keys:
@@ -93,7 +105,14 @@ class VelaOmni(nn.Module):
93
  if any(len(w) > 30 * sampling_rate for w in waveforms):
94
  raise ValueError("Audio inputs must be at most 30 seconds")
95
  if self.variant == "nano":
96
- values = self.model.encode_audio(waveforms, sampling_rate=sampling_rate)
 
 
 
 
 
 
 
97
  else:
98
  features = self.model.audio_processor(waveforms, sampling_rate=sampling_rate,
99
  return_tensors="pt")["input_features"]
 
8
  from torch import nn
9
  from torch.nn import functional as F
10
 
11
+ from omni_components.residual_projection import ResidualTextProjection, duration_mean
12
  from omni_components.embedder import MultimodalEmbedder
13
  from omni_components.mini import MultiModalSentenceEmbedder
14
 
15
 
16
  class VelaOmni(nn.Module):
17
+ def __init__(self, root, variant, legacy=False, audio_projection="identity", text_projection="identity", audio_pooling="mean"):
18
  super().__init__()
19
  self.variant = variant
20
+ if text_projection not in ("identity", "residual_rank64"):
21
+ raise ValueError("Unsupported text projection")
22
+ if audio_pooling not in ("mean", "duration_mean") or (variant != "nano" and audio_pooling != "mean"):
23
+ raise ValueError("Unsupported audio pooling")
24
+ self.audio_pooling = audio_pooling
25
  self.embedding_dim = 384 if variant == "nano" else 768
26
  self.max_text_length = 128 if variant == "nano" else 32768
27
  if variant == "nano":
28
  self.model = MultimodalEmbedder(Path(root) / "components", legacy=legacy)
29
  self.tokenizer = self.model.text_encoder.tokenizer
30
+ if text_projection == "residual_rank64":
31
+ self.model.text_encoder.projection = ResidualTextProjection(384)
32
  if audio_projection == "linear":
33
  self.model.audio_encoder.projection = nn.Linear(384, 384)
34
  elif audio_projection != "identity":
 
38
  raise ValueError("Mini uses its native audio projection")
39
  self.model = MultiModalSentenceEmbedder(Path(root) / "components", legacy=legacy)
40
  self.tokenizer = self.model.text_model.tokenizer
41
+ if text_projection == "residual_rank64":
42
+ self.model.text_proj = ResidualTextProjection(768)
43
  else:
44
  raise ValueError("Unknown Vela Omni variant")
45
 
 
51
  root = Path(snapshot_download(repo_id, revision=revision,
52
  allow_patterns=["config.json", "model.safetensors", "components/*"]))
53
  config = json.loads((root / "config.json").read_text())
54
+ if config.get("format_version") not in (1, 2, 3):
55
  raise ValueError("Unsupported Vela Omni artifact format")
56
+ instance = cls(root, config["variant"], audio_projection=config.get("audio_projection", "identity"),
57
+ text_projection=config.get("text_projection", "identity"),
58
+ audio_pooling=config.get("audio_pooling", "mean"))
59
  weights = load_file(str(root / "model.safetensors"), device="cpu")
60
  result = instance.model.load_state_dict(weights, strict=True, assign=True)
61
  if result.missing_keys or result.unexpected_keys:
 
105
  if any(len(w) > 30 * sampling_rate for w in waveforms):
106
  raise ValueError("Audio inputs must be at most 30 seconds")
107
  if self.variant == "nano":
108
+ if self.audio_pooling == "duration_mean":
109
+ encoder = self.model.audio_encoder
110
+ features = encoder.feature_extractor(waveforms, sampling_rate=sampling_rate,
111
+ return_tensors="pt")["input_features"].to(next(encoder.parameters()))
112
+ hidden = encoder.encoder(input_features=features, output_hidden_states=False).last_hidden_state
113
+ values = encoder.projection(duration_mean(hidden, [len(w) for w in waveforms]))
114
+ else:
115
+ values = self.model.encode_audio(waveforms, sampling_rate=sampling_rate)
116
  else:
117
  features = self.model.audio_processor(waveforms, sampling_rate=sampling_rate,
118
  return_tensors="pt")["input_features"]