latimal commited on
Commit
04fb3cf
·
verified ·
1 Parent(s): 24a0a66

refactor: tables format, dark theme, food-embed.latimal.com

Browse files
Files changed (2) hide show
  1. README.md +2 -2
  2. index.html +255 -398
README.md CHANGED
@@ -1,9 +1,9 @@
1
  ---
2
- title: Latimal Menu Intelligence Benchmarks
3
  emoji: 🍽️
4
  colorFrom: green
5
  colorTo: blue
6
  sdk: static
7
  pinned: false
8
- short_description: Benchmark results for Latimal Menu Intelligence
9
  ---
 
1
  ---
2
+ title: Latimal Production Benchmarks
3
  emoji: 🍽️
4
  colorFrom: green
5
  colorTo: blue
6
  sdk: static
7
  pinned: false
8
+ short_description: Production pipeline benchmarks for Latimal Menu Intelligence
9
  ---
index.html CHANGED
@@ -3,490 +3,347 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Latimal Menu Intelligence: Benchmark Results</title>
7
  <style>
8
- :root {
9
- --bg: #ffffff;
10
- --text: #1a1a2e;
11
- --muted: #6b7280;
12
- --green: #16a34a;
13
- }
14
  * { box-sizing: border-box; margin: 0; padding: 0; }
15
  body {
16
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
17
- background: var(--bg);
18
- color: var(--text);
19
- max-width: 1100px;
20
  margin: 0 auto;
21
- padding: 36px 32px 28px;
 
22
  }
23
 
24
- /* Header */
25
- .header { margin-bottom: 24px; }
26
  .header h1 {
27
- font-size: 1.5rem;
28
  font-weight: 700;
29
  letter-spacing: -0.02em;
30
- margin-bottom: 5px;
31
  }
32
  .header .subtitle {
33
- font-size: 0.85rem;
34
- color: var(--muted);
35
- line-height: 1.4;
 
36
  }
37
 
38
- /* Legend */
39
- .legend {
40
- display: flex;
41
- gap: 14px;
42
- flex-wrap: wrap;
43
- margin-bottom: 22px;
44
- padding: 10px 14px;
45
- background: #f9fafb;
46
- border-radius: 6px;
47
- border: 1px solid #e5e7eb;
48
- }
49
- .legend-item {
50
- display: flex;
51
- align-items: center;
52
- gap: 5px;
53
- font-size: 0.68rem;
54
- color: #374151;
55
- white-space: nowrap;
56
- }
57
- .legend-desc {
58
- font-size: 0.58rem;
59
- color: #9ca3af;
60
  }
61
- .legend-dot {
62
- width: 10px;
63
- height: 10px;
64
- border-radius: 2px;
65
- flex-shrink: 0;
 
66
  }
67
- .legend-sep {
68
- width: 1px;
69
- height: 18px;
70
- background: #d1d5db;
71
- align-self: center;
72
  }
73
-
74
- /* Section group headers */
75
- .section-group {
76
- grid-column: 1 / -1;
77
- margin-top: 10px;
78
- padding-top: 16px;
79
- border-top: 1px solid #e5e7eb;
80
- }
81
- .section-group:first-child {
82
- margin-top: 0;
83
- padding-top: 0;
84
- border-top: none;
85
  }
86
- .section-group-title {
87
- font-size: 0.92rem;
 
 
 
88
  font-weight: 700;
89
- color: var(--text);
90
- margin-bottom: 3px;
91
  }
92
- .section-group-note {
93
- font-size: 0.72rem;
94
- color: #9ca3af;
95
- line-height: 1.4;
96
- margin-bottom: 4px;
97
  }
98
 
99
- /* Two-column grid */
100
- .charts-grid {
101
- display: grid;
102
- grid-template-columns: 1fr 1fr;
103
- gap: 20px 28px;
104
  }
105
 
106
- /* Benchmark section */
107
- .benchmark {}
108
- .bench-header {
109
- display: flex;
110
- justify-content: space-between;
111
- align-items: baseline;
112
- margin-bottom: 8px;
113
- }
114
- .bench-title {
115
- font-size: 0.85rem;
116
- font-weight: 600;
117
- }
118
- .bench-metric {
119
- font-size: 0.68rem;
120
- color: var(--muted);
121
- font-weight: 500;
122
  }
123
 
124
- /* Bar rows */
125
- .bar-row {
126
- display: flex;
127
- align-items: center;
128
- margin-bottom: 4px;
129
  }
130
- .bar-label {
131
- width: 110px;
132
- font-size: 0.72rem;
133
- color: #6b7280;
134
- flex-shrink: 0;
135
  text-align: right;
136
- padding-right: 10px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
137
  }
138
- .bar-track {
139
- flex: 1;
140
- height: 22px;
141
- background: #f3f4f6;
142
- border-radius: 3px;
143
- position: relative;
144
- overflow: hidden;
145
  }
146
- .bar-fill {
147
- height: 100%;
148
- border-radius: 3px;
149
- position: relative;
150
  }
151
- .bar-value {
152
- position: absolute;
153
- right: 6px;
154
- top: 50%;
155
- transform: translateY(-50%);
156
- font-size: 0.68rem;
157
- font-weight: 700;
158
- color: white;
159
- text-shadow: 0 1px 2px rgba(0,0,0,0.2);
160
  }
161
- .bar-value-outside {
162
- position: absolute;
163
- left: calc(var(--bar-width) + 6px);
164
- top: 50%;
165
- transform: translateY(-50%);
166
- font-size: 0.68rem;
167
- font-weight: 600;
168
- color: #374151;
169
  }
170
- .bar-row.best .bar-label {
 
171
  font-weight: 700;
172
- color: var(--text);
173
  }
174
 
175
- /* Glossary */
176
- .glossary {
177
- margin-top: 22px;
178
- padding-top: 14px;
179
- border-top: 1px solid #e5e7eb;
180
- }
181
- .glossary-title {
182
- font-size: 0.78rem;
183
- font-weight: 700;
184
- color: var(--text);
185
- margin-bottom: 8px;
 
 
 
 
 
 
 
 
186
  }
187
- .glossary-grid {
188
- display: grid;
189
- grid-template-columns: 1fr 1fr;
190
- gap: 4px 28px;
191
  }
192
- .glossary-item {
193
- font-size: 0.68rem;
194
- color: #6b7280;
195
- line-height: 1.5;
196
  }
197
- .glossary-item strong {
198
- color: #374151;
199
- margin-right: 4px;
200
  }
201
-
202
- /* Section annotation */
203
- .section-annotation {
204
- font-size: 0.68rem;
205
- color: #64748b;
206
- background: #f1f5f9;
207
- border-radius: 4px;
208
- padding: 6px 12px;
209
- margin-bottom: 4px;
210
- line-height: 1.4;
211
  }
212
 
213
- /* Footer */
214
  .footer {
215
- margin-top: 22px;
216
- padding-top: 14px;
217
- border-top: 1px solid #e5e7eb;
218
- font-size: 0.68rem;
219
- color: #9ca3af;
220
- line-height: 1.5;
221
  display: flex;
222
  justify-content: space-between;
223
  gap: 24px;
224
  }
225
- .footer-left { flex: 1; }
 
 
 
 
226
  .footer-right { text-align: right; white-space: nowrap; }
227
 
228
- /* Mobile */
229
  @media (max-width: 700px) {
230
- body { padding: 20px 14px 18px; }
231
- .header h1 { font-size: 1.15rem; }
232
- .legend { gap: 8px 12px; padding: 8px 12px; }
233
- .legend-item { font-size: 0.7rem; }
234
- .legend-sep { display: none; }
235
- .charts-grid {
236
- grid-template-columns: 1fr;
237
- gap: 18px;
238
- }
239
- .glossary-grid { grid-template-columns: 1fr; }
240
- .bench-header { flex-wrap: wrap; gap: 2px 8px; }
241
- .bar-label {
242
- width: 72px;
243
- font-size: 0.65rem;
244
- padding-right: 6px;
245
- }
246
- .bar-track { height: 20px; }
247
- .bar-value { font-size: 0.6rem; right: 4px; }
248
- .bar-value-outside { font-size: 0.6rem; }
249
- .bench-title { font-size: 0.78rem; }
250
- .bench-metric { font-size: 0.62rem; }
251
- .section-group-title { font-size: 0.82rem; }
252
- .section-group-note { font-size: 0.65rem; }
253
  .footer { flex-direction: column; gap: 8px; }
254
  .footer-right { text-align: left; }
 
 
 
 
 
255
  }
256
  </style>
257
  </head>
258
  <body>
259
 
260
  <div class="header">
261
- <h1>Latimal Menu Intelligence: Food Benchmark Results</h1>
262
- <p class="subtitle">Domain-specialized food embedding model vs general-purpose alternatives. All models evaluated at 384 dimensions on identical benchmark data.</p>
263
- </div>
264
-
265
- <div class="legend">
266
- <div class="legend-item"><div class="legend-dot" style="background:#16a34a"></div>Latimal</div>
267
- <div class="legend-item"><div class="legend-dot" style="background:#ea580c"></div>OpenAI TE3-Large</div>
268
- <div class="legend-item"><div class="legend-dot" style="background:#6b7280"></div>BAAI BGE-M3</div>
269
- <div class="legend-item"><div class="legend-dot" style="background:#2563eb"></div>Qwen3-Embedding-0.6B <span class="legend-desc">#1 MTEB Multilingual</span></div>
270
- <div class="legend-item"><div class="legend-dot" style="background:#9333ea"></div>Microsoft E5-Large-v2</div>
271
- <div class="legend-item"><div class="legend-dot" style="background:#64748b"></div>BGE-Reranker-v2-M3 <span class="legend-desc">Best public reranker</span></div>
272
  </div>
273
 
274
- <div id="charts" class="charts-grid"></div>
275
-
276
- <div class="glossary">
277
- <div class="glossary-title">Benchmark Glossary</div>
278
- <div class="glossary-grid">
279
- <div class="glossary-item"><strong>Indian Cuisine Matching</strong> Matching "Aloo Gobi" to "Potato Cauliflower Curry", "Dal Makhani" to "Black Lentil Curry" across restaurants.</div>
280
- <div class="glossary-item"><strong>Cross-Language Matching</strong> Matching "ラーメン" to "Ramen", "خبز نان" to "Naan Bread" across languages and scripts.</div>
281
- <div class="glossary-item"><strong>Bakery &amp; Dessert Matching</strong> Matching "Pain au Chocolat" to "Chocolate Croissant", "Crème Brûlée" to "Caramelized Custard".</div>
282
- <div class="glossary-item"><strong>Beverage Matching</strong> Matching "Iced Americano" to "Cold Black Coffee", "Masala Chai" to "Spiced Tea Latte" across naming conventions.</div>
283
- <div class="glossary-item"><strong>Synonym Recognition</strong> Retrieving "Pad Kra Pao" from a query for "Thai Basil Stir-Fry", or "Gyoza" from "Pot Stickers".</div>
284
- <div class="glossary-item"><strong>Cuisine Classification</strong> Classifying "Tom Yum Goong" as Thai, "Cacio e Pepe" as Italian from the dish name alone. 19 cuisine categories.</div>
285
- <div class="glossary-item"><strong>Category Search</strong> Searching "Thai soups" or "grilled appetizers" and ranking relevant menu items.</div>
286
- <div class="glossary-item"><strong>Typo-Tolerant Search</strong> Returning "Margherita Pizza" when a customer types "margarita piza".</div>
287
- <div class="glossary-item"><strong>Food Search</strong> General menu search ranking across diverse food queries and item catalogs.</div>
288
- <div class="glossary-item"><strong>Global Search</strong> Search across multilingual menus spanning 15+ cuisines worldwide.</div>
289
- <div class="glossary-item"><strong>Portion Size Sensitivity</strong> Ignoring portion labels like "Regular", "Family Pack", "Serves 2", "250ml" when matching the same dish. Generic models treat size text as meaningful content.</div>
290
- <div class="glossary-item"><strong>Noisy Menu Matching</strong> Matching "***BEST SELLER*** Paneer Tikka - Chef's Special!!" to "Paneer Tikka" on another menu.</div>
291
- <div class="glossary-item"><strong>Bilingual Menu Matching</strong> Matching "Falafel Wrap فلافل راب" to "Falafel Wrap" on menus that mix scripts.</div>
292
- <div class="glossary-item"><strong>Embedding Stability</strong> Producing identical embeddings for "Fried Rice", "炒飯", and "フライドライス". 1.0 = perfectly consistent across scripts.</div>
293
- </div>
294
- </div>
295
 
296
  <div class="footer">
297
- <div class="footer-left">All competing models paired with BGE-Reranker-v2-M3, the strongest publicly available reranker.</div>
298
- <div class="footer-right">May 2026<br>Latimal Menu Intelligence &middot; latimal.com<br><a href="mailto:aditya@latimal.com" style="color:#9ca3af;text-decoration:none">aditya@latimal.com</a></div>
299
  </div>
300
 
301
  <script>
302
- const FULL_MODELS = [
303
- { key: "dish_embed", label: "Latimal", color: "#16a34a" },
304
- { key: "openai", label: "OpenAI TE3L", color: "#ea580c" },
305
- { key: "bge_m3", label: "BGE-M3", color: "#6b7280" },
306
- { key: "qwen3", label: "Qwen3-0.6B", color: "#2563eb" },
307
- { key: "e5_large", label: "E5-Large-v2", color: "#9333ea" },
 
 
308
  ];
309
 
310
- const COLLAPSED_MODELS = [
311
- { key: "dish_embed", label: "Latimal", color: "#16a34a" },
312
- { key: "off_shelf", label: "All others", color: "#64748b" },
313
- ];
314
-
315
- const RERANKER_MODELS = [
316
- { key: "dish_embed", label: "Latimal", color: "#16a34a", isOurs: true },
317
- { key: "openai", label: "OpenAI TE3L", color: "#64748b" },
318
- { key: "bge_m3", label: "BGE-M3", color: "#64748b" },
319
- { key: "qwen3", label: "Qwen3-0.6B", color: "#64748b" },
320
- { key: "e5_large", label: "E5-Large-v2", color: "#64748b" },
321
- ];
322
-
323
- const SECTIONS = [
324
  {
325
- title: "Food Understanding",
326
- note: "Core food knowledge that powers synonym-aware search, cuisine tagging, and regional variant detection.",
327
- mode: "full",
328
- benchmarks: [
329
- {
330
- title: "Synonym Recognition",
331
- metric: "Recall@5",
332
- scores: { dish_embed: 0.835, openai: 0.749, bge_m3: 0.707, qwen3: 0.514, e5_large: 0.661 }
333
- },
334
- {
335
- title: "Cuisine Classification (19 cuisines)",
336
- metric: "Macro Accuracy",
337
- scores: { dish_embed: 0.889, openai: 0.822, bge_m3: 0.762, qwen3: 0.439, e5_large: 0.298 }
338
- },
339
- ]
340
  },
341
  {
342
- title: "Menu Search",
343
- note: "Ranking relevant menu items when customers search for 'Thai soups' or type 'chiken tikka' with a typo.",
344
- mode: "full",
345
- benchmarks: [
346
- {
347
- title: "Category Search",
348
- metric: "NDCG@10",
349
- scores: { dish_embed: 0.814, openai: 0.797, bge_m3: 0.759, qwen3: 0.802, e5_large: 0.799 }
350
- },
351
- {
352
- title: "Typo-Tolerant Search",
353
- metric: "NDCG@10",
354
- scores: { dish_embed: 0.912, openai: 0.884, bge_m3: 0.902, qwen3: 0.892, e5_large: 0.907 }
355
- },
356
- {
357
- title: "Food Search",
358
- metric: "NDCG@10",
359
- scores: { dish_embed: 0.944, openai: 0.925, bge_m3: 0.929, qwen3: 0.935, e5_large: 0.939 }
360
- },
361
- {
362
- title: "Global Search",
363
- metric: "NDCG@10",
364
- scores: { dish_embed: 0.928, openai: 0.839, bge_m3: 0.886, qwen3: 0.875, e5_large: 0.860 }
365
- }
366
- ]
367
  },
368
  {
369
- title: "Cross-Restaurant Item Matching",
370
- note: "Matching 'Gyoza' to 'Pot Stickers', 'Crème Brûlée' to 'Burnt Cream Custard', 'Dal Makhani' to 'Black Lentil Curry' across thousands of restaurants. Powers price comparison, catalog consolidation, and menu analytics.",
371
- annotation: "OpenAI TE3-Large, BAAI BGE-M3, Qwen3-Embedding-0.6B, and Microsoft E5-Large-v2 all paired with BGE-Reranker-v2-M3. The reranker determines matching quality, so all embedding models produce identical scores.",
372
- mode: "collapsed",
373
- benchmarks: [
374
- {
375
- title: "Indian Cuisine Matching",
376
- metric: "F1",
377
- scores: { dish_embed: 0.919, off_shelf: 0.754 }
378
- },
379
- {
380
- title: "Cross-Language Matching",
381
- metric: "F1",
382
- scores: { dish_embed: 0.814, off_shelf: 0.258 }
383
- },
384
- {
385
- title: "Bakery & Dessert Matching",
386
- metric: "F1",
387
- scores: { dish_embed: 0.783, off_shelf: 0.655 }
388
- },
389
- {
390
- title: "Beverage Matching",
391
- metric: "F1",
392
- scores: { dish_embed: 0.744, off_shelf: 0.648 }
393
- },
394
- ]
395
  },
396
  {
397
- title: "Robustness",
398
- note: "Consistent performance across portion sizes, formatting differences, and platform-specific conventions.",
399
- mode: "mixed",
400
- benchmarks: [
401
- {
402
- title: "Portion Size Sensitivity",
403
- metric: "F1",
404
- mode: "collapsed",
405
- scores: { dish_embed: 0.885, off_shelf: 0.082 }
406
- },
407
- {
408
- title: "Noisy Menu Matching",
409
- metric: "F1",
410
- mode: "collapsed",
411
- scores: { dish_embed: 0.926, off_shelf: 0.914 }
412
- },
413
- {
414
- title: "Bilingual Menu Matching",
415
- metric: "F1",
416
- mode: "collapsed",
417
- scores: { dish_embed: 0.907, off_shelf: 0.879 }
418
- },
419
- {
420
- title: "Embedding Stability",
421
- metric: "Cosine Similarity",
422
- mode: "full",
423
- scores: { dish_embed: 1.000, openai: 0.000, bge_m3: 0.506, qwen3: 0.001, e5_large: 0.170 }
424
- }
425
- ]
426
- }
427
  ];
428
 
429
- const container = document.getElementById("charts");
430
 
431
- SECTIONS.forEach((section) => {
432
- const groupEl = document.createElement("div");
433
- groupEl.className = "section-group";
434
- groupEl.innerHTML = `
435
- <div class="section-group-title">${section.title}</div>
436
- <div class="section-group-note">${section.note}</div>
437
- ${section.annotation ? `<div class="section-annotation">${section.annotation}</div>` : ''}
438
- `;
439
- container.appendChild(groupEl);
440
 
441
- section.benchmarks.forEach(bench => {
442
- const benchMode = bench.mode || section.mode || "full";
443
- const models = benchMode === "reranker" ? RERANKER_MODELS : benchMode === "collapsed" ? COLLAPSED_MODELS : FULL_MODELS;
444
- const filteredModels = bench.filterZero
445
- ? models.filter(m => bench.scores[m.key] != null && bench.scores[m.key] > 0)
446
- : models;
447
- const activeScores = filteredModels.map(m => bench.scores[m.key]).filter(s => s != null);
448
- const best = Math.max(...activeScores);
449
- const el = document.createElement("div");
450
- el.className = "benchmark";
451
 
452
- let html = `
453
- <div class="bench-header">
454
- <div class="bench-title">${bench.title}</div>
455
- <div class="bench-metric">${bench.metric}</div>
456
- </div>`;
457
 
458
- const sorted = [...filteredModels].sort((a, b) => (bench.scores[b.key] || 0) - (bench.scores[a.key] || 0));
 
 
 
459
 
460
- let addedRerankerLabel = false;
461
- sorted.forEach(model => {
462
- const score = bench.scores[model.key];
463
- if (score == null) return;
464
- const pct = (score * 100).toFixed(1);
465
- const barWidth = Math.max(0, (score / 1.0) * 100);
466
- const isBest = Math.abs(score - best) < 0.0001;
467
- const bestClass = isBest ? " best" : "";
468
- const valueInside = barWidth > 25;
469
 
470
- if (benchMode === "reranker" && !model.isOurs && !addedRerankerLabel) {
471
- html += `<div class="reranker-label">with BGE-Reranker-v2-M3</div>`;
472
- addedRerankerLabel = true;
473
- }
 
474
 
475
- html += `
476
- <div class="bar-row${bestClass}">
477
- <div class="bar-label">${model.label}</div>
478
- <div class="bar-track">
479
- <div class="bar-fill" style="width:${barWidth}%;background:${model.color}${isBest ? '' : 'cc'}">
480
- ${valueInside ? `<span class="bar-value">${pct}</span>` : ''}
481
- </div>
482
- ${!valueInside ? `<span class="bar-value-outside" style="--bar-width:${barWidth}%">${pct}</span>` : ''}
483
- </div>
484
- </div>`;
485
  });
486
 
487
- el.innerHTML = html;
488
- container.appendChild(el);
489
  });
 
 
 
 
 
 
490
  });
491
  </script>
492
 
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Latimal Production Benchmarks</title>
7
  <style>
 
 
 
 
 
 
8
  * { box-sizing: border-box; margin: 0; padding: 0; }
9
  body {
10
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
11
+ background: #09090b;
12
+ color: #fafafa;
13
+ max-width: 1200px;
14
  margin: 0 auto;
15
+ padding: 40px 32px 32px;
16
+ -webkit-font-smoothing: antialiased;
17
  }
18
 
19
+ .header { margin-bottom: 32px; }
 
20
  .header h1 {
21
+ font-size: 1.6rem;
22
  font-weight: 700;
23
  letter-spacing: -0.02em;
24
+ margin-bottom: 8px;
25
  }
26
  .header .subtitle {
27
+ font-size: 0.88rem;
28
+ color: #a1a1aa;
29
+ line-height: 1.5;
30
+ max-width: 680px;
31
  }
32
 
33
+ .section { margin-bottom: 40px; }
34
+ .section-title {
35
+ font-size: 1.15rem;
36
+ font-weight: 700;
37
+ margin-bottom: 4px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  }
39
+ .section-caption {
40
+ font-size: 0.78rem;
41
+ color: #71717a;
42
+ line-height: 1.5;
43
+ margin-bottom: 6px;
44
+ max-width: 640px;
45
  }
46
+ .section-bullets {
47
+ list-style: none;
48
+ padding: 0;
49
+ margin-bottom: 12px;
 
50
  }
51
+ .section-bullets li {
52
+ font-size: 0.75rem;
53
+ color: #71717a;
54
+ line-height: 1.5;
55
+ padding-left: 14px;
56
+ position: relative;
 
 
 
 
 
 
57
  }
58
+ .section-bullets li::before {
59
+ content: "·";
60
+ position: absolute;
61
+ left: 2px;
62
+ color: #52525b;
63
  font-weight: 700;
 
 
64
  }
65
+ .section-annot {
66
+ font-size: 0.75rem;
67
+ color: #a1a1aa;
68
+ margin-top: 8px;
 
69
  }
70
 
71
+ .scroll-wrap {
72
+ overflow-x: auto;
73
+ -webkit-overflow-scrolling: touch;
 
 
74
  }
75
 
76
+ table {
77
+ width: 100%;
78
+ border-collapse: collapse;
79
+ font-size: 0.82rem;
80
+ border: 1px solid #27272a;
81
+ border-radius: 8px;
82
+ overflow: hidden;
83
+ font-variant-numeric: tabular-nums;
 
 
 
 
 
 
 
 
84
  }
85
 
86
+ thead tr {
87
+ background: #18181b;
88
+ border-bottom: 1px solid #27272a;
 
 
89
  }
90
+ th {
91
+ padding: 10px 14px;
 
 
 
92
  text-align: right;
93
+ font-weight: 600;
94
+ white-space: nowrap;
95
+ vertical-align: bottom;
96
+ }
97
+ th.task-col {
98
+ text-align: left;
99
+ position: sticky;
100
+ left: 0;
101
+ z-index: 2;
102
+ background: #18181b;
103
+ border-right: 1px solid #27272a;
104
+ }
105
+ th .co {
106
+ display: block;
107
+ font-size: 0.65rem;
108
+ font-weight: 400;
109
+ color: #71717a;
110
+ line-height: 1.3;
111
+ }
112
+ th .model {
113
+ display: block;
114
+ color: #e4e4e7;
115
+ line-height: 1.4;
116
  }
117
+ th.lead-col {
118
+ color: #fbbf24;
 
 
 
 
 
119
  }
120
+ th.lead-col .model {
121
+ color: #fbbf24;
 
 
122
  }
123
+
124
+ tbody tr {
125
+ border-bottom: 1px solid rgba(39,39,42,0.5);
 
 
 
 
 
 
126
  }
127
+ tbody tr:last-child { border-bottom: none; }
128
+ tbody tr.avg-row {
129
+ border-top: 2px solid #3f3f46;
 
 
 
 
 
130
  }
131
+ tbody tr.avg-row td,
132
+ tbody tr.avg-row th {
133
  font-weight: 700;
 
134
  }
135
 
136
+ td {
137
+ padding: 9px 14px;
138
+ text-align: right;
139
+ white-space: nowrap;
140
+ color: #71717a;
141
+ }
142
+ td.task-cell {
143
+ text-align: left;
144
+ position: sticky;
145
+ left: 0;
146
+ z-index: 1;
147
+ background: #09090b;
148
+ border-right: 1px solid #27272a;
149
+ color: #d4d4d8;
150
+ }
151
+ td.task-cell .sub {
152
+ display: block;
153
+ font-size: 0.68rem;
154
+ color: #52525b;
155
  }
156
+ .avg-row td.task-cell { color: #fafafa; }
157
+
158
+ td.lead {
159
+ color: #a1a1aa;
160
  }
161
+ td.lead-win {
162
+ color: #fbbf24;
163
+ font-weight: 600;
 
164
  }
165
+ td.comp-win {
166
+ color: #e4e4e7;
167
+ font-weight: 600;
168
  }
169
+ .avg-row td.lead { color: #d4d4d8; }
170
+ .avg-row td.lead-win { color: #fbbf24; font-weight: 700; }
171
+ .avg-row td:not(.task-cell):not(.lead):not(.lead-win):not(.comp-win) {
172
+ color: #a1a1aa;
 
 
 
 
 
 
173
  }
174
 
 
175
  .footer {
176
+ margin-top: 32px;
177
+ padding-top: 16px;
178
+ border-top: 1px solid #27272a;
179
+ font-size: 0.72rem;
180
+ color: #52525b;
181
+ line-height: 1.6;
182
  display: flex;
183
  justify-content: space-between;
184
  gap: 24px;
185
  }
186
+ .footer a {
187
+ color: #71717a;
188
+ text-decoration: none;
189
+ }
190
+ .footer a:hover { color: #a1a1aa; }
191
  .footer-right { text-align: right; white-space: nowrap; }
192
 
 
193
  @media (max-width: 700px) {
194
+ body { padding: 20px 12px 20px; }
195
+ .header h1 { font-size: 1.2rem; }
196
+ th, td { padding: 7px 10px; font-size: 0.75rem; }
197
+ th .co { font-size: 0.6rem; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
198
  .footer { flex-direction: column; gap: 8px; }
199
  .footer-right { text-align: left; }
200
+ td.task-cell .name {
201
+ display: block;
202
+ max-width: 36vw;
203
+ white-space: normal;
204
+ }
205
  }
206
  </style>
207
  </head>
208
  <body>
209
 
210
  <div class="header">
211
+ <h1>Latimal Production Benchmarks</h1>
212
+ <p class="subtitle">How the full Latimal production pipeline compares to leading embedding models on food-domain tasks. All models at 384 dimensions.</p>
 
 
 
 
 
 
 
 
 
213
  </div>
214
 
215
+ <div id="tables"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
216
 
217
  <div class="footer">
218
+ <div>All models compared at 384 dimensions. Competitors paired with <a href="https://huggingface.co/BAAI/bge-reranker-v2-m3">bge-reranker-v2-m3</a>. Latimal: production API, measured at the public API boundary, reproducible with an API key.</div>
219
+ <div class="footer-right">June 2026<br><a href="https://latimal.com">latimal.com</a></div>
220
  </div>
221
 
222
  <script>
223
+ const COMPETITORS = [
224
+ { company: "OpenAI", model: "text-embedding-3-large" },
225
+ { company: "Voyage AI", model: "Voyage 4 Large" },
226
+ { company: "Cohere", model: "Embed v4" },
227
+ { company: "Alibaba", model: "GTE-large" },
228
+ { company: "Nomic AI", model: "Nomic v1.5" },
229
+ { company: "BAAI", model: "BGE-M3" },
230
+ { company: "Microsoft", model: "E5-large" },
231
  ];
232
 
233
+ const TABLES = [
 
 
 
 
 
 
 
 
 
 
 
 
 
234
  {
235
+ title: "Overall",
236
+ caption: "Category averages across the three tables below.",
237
+ bullets: [
238
+ "Search: production API. Matching and classification: raw embeddings + cosine.",
239
+ "Average: unweighted mean of the three category scores.",
240
+ ],
241
+ leadCompany: "Latimal", leadModel: "Food Embed v1",
242
+ rows: [
243
+ { task: "Average", sub: "3 categories", scores: [0.819, 0.682, 0.659, 0.643, 0.614, 0.624, 0.609, 0.505], avg: true },
244
+ { task: "Search", sub: "Production NDCG@10, 4 tasks", scores: [0.869, 0.455, 0.447, 0.451, 0.427, 0.422, 0.408, 0.411] },
245
+ { task: "Matching", sub: "Mean F1, 7 tasks", scores: [0.851, 0.758, 0.741, 0.741, 0.699, 0.739, 0.718, 0.704] },
246
+ { task: "Classification", sub: "Macro F1, 1 task", scores: [0.738, 0.833, 0.789, 0.737, 0.716, 0.71, 0.701, 0.399] },
247
+ ],
 
 
248
  },
249
  {
250
+ title: "Matching",
251
+ caption: "Best F1, 7 tasks.",
252
+ bullets: [
253
+ "Same-dish detection across cuisines, scripts, and noise levels.",
254
+ "Raw embeddings + cosine, no reranking.",
255
+ ],
256
+ leadCompany: "Latimal", leadModel: "Food Embed v1",
257
+ rows: [
258
+ { task: "Average", sub: "7 tasks", scores: [0.851, 0.758, 0.741, 0.741, 0.699, 0.739, 0.718, 0.704], avg: true },
259
+ { task: "Indian cuisine", scores: [0.817, 0.745, 0.718, 0.732, 0.705, 0.731, 0.711, 0.68] },
260
+ { task: "Global cuisine", scores: [0.867, 0.828, 0.783, 0.829, 0.695, 0.732, 0.716, 0.716] },
261
+ { task: "Beverages", scores: [0.746, 0.715, 0.719, 0.71, 0.71, 0.715, 0.706, 0.706] },
262
+ { task: "Bakery & desserts", scores: [0.755, 0.735, 0.715, 0.691, 0.682, 0.684, 0.684, 0.688] },
263
+ { task: "Portion size", scores: [0.972, 0.849, 0.791, 0.835, 0.725, 0.855, 0.821, 0.757] },
264
+ { task: "Noisy menu", scores: [0.916, 0.685, 0.64, 0.667, 0.672, 0.75, 0.674, 0.648] },
265
+ { task: "Cross-lingual", scores: [0.886, 0.748, 0.82, 0.721, 0.707, 0.707, 0.717, 0.731] },
266
+ ],
 
 
 
 
 
 
 
 
267
  },
268
  {
269
+ title: "Search",
270
+ caption: "Production search, NDCG@10.",
271
+ bullets: [
272
+ "Latimal: production API, measured at the public API boundary. Reproducible with an API key.",
273
+ "Competitors: embedding + bge-reranker-v2-m3.",
274
+ ],
275
+ leadModel: "Latimal",
276
+ rows: [
277
+ { task: "Average", sub: "4 tasks", scores: [0.869, 0.455, 0.447, 0.451, 0.427, 0.422, 0.408, 0.411], avg: true },
278
+ { task: "Food search", sub: "NDCG@10", scores: [0.938, 0.59, 0.59, 0.589, 0.572, 0.564, 0.552, 0.554] },
279
+ { task: "Concept search", sub: "NDCG@10", scores: [0.809, 0.405, 0.392, 0.391, 0.374, 0.357, 0.336, 0.328] },
280
+ { task: "Diet & allergen search", sub: "NDCG@10", scores: [0.802, 0.172, 0.161, 0.165, 0.135, 0.132, 0.132, 0.136] },
281
+ { task: "Noisy search", sub: "NDCG@10", scores: [0.925, 0.653, 0.644, 0.66, 0.628, 0.635, 0.614, 0.628] },
282
+ ],
283
+ annotation: "Diet & allergen search: 4.7x the best competitor.",
 
 
 
 
 
 
 
 
 
 
 
284
  },
285
  {
286
+ title: "Classification",
287
+ caption: "Macro F1, 1 task. Linear probe on frozen embeddings, 26 menu classes.",
288
+ leadCompany: "Latimal", leadModel: "Food Embed v1",
289
+ rows: [
290
+ { task: "Cuisine classification", sub: "Macro F1", scores: [0.738, 0.833, 0.789, 0.737, 0.716, 0.71, 0.701, 0.399] },
291
+ ],
292
+ },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
293
  ];
294
 
295
+ const container = document.getElementById("tables");
296
 
297
+ TABLES.forEach(t => {
298
+ const section = document.createElement("div");
299
+ section.className = "section";
 
 
 
 
 
 
300
 
301
+ let html = `<div class="section-title">${t.title}</div>`;
302
+ html += `<p class="section-caption">${t.caption}</p>`;
303
+ if (t.bullets) {
304
+ html += '<ul class="section-bullets">';
305
+ t.bullets.forEach(b => { html += `<li>${b}</li>`; });
306
+ html += '</ul>';
307
+ }
 
 
 
308
 
309
+ html += '<div class="scroll-wrap"><table><thead><tr>';
310
+ html += '<th class="task-col"><span class="model">Task</span></th>';
 
 
 
311
 
312
+ const leadLabel = t.leadCompany
313
+ ? `<span class="co">${t.leadCompany}</span><span class="model">${t.leadModel}</span>`
314
+ : `<span class="model">${t.leadModel}</span>`;
315
+ html += `<th class="lead-col">${leadLabel}</th>`;
316
 
317
+ COMPETITORS.forEach(c => {
318
+ html += `<th><span class="co">${c.company}</span><span class="model">${c.model}</span></th>`;
319
+ });
320
+ html += '</tr></thead><tbody>';
 
 
 
 
 
321
 
322
+ t.rows.forEach(row => {
323
+ const best = Math.max(...row.scores);
324
+ const rowCls = row.avg ? ' class="avg-row"' : '';
325
+ html += `<tr${rowCls}>`;
326
+ html += `<td class="task-cell"><span class="name">${row.task}</span>${row.sub ? `<span class="sub">${row.sub}</span>` : ''}</td>`;
327
 
328
+ row.scores.forEach((score, i) => {
329
+ const isBest = Math.abs(score - best) < 1e-9;
330
+ const isLead = i === 0;
331
+ let cls;
332
+ if (isBest && isLead) cls = "lead-win";
333
+ else if (isBest && !isLead) cls = "comp-win";
334
+ else if (isLead) cls = "lead";
335
+ else cls = "";
336
+ html += `<td class="${cls}">${score.toFixed(3)}</td>`;
 
337
  });
338
 
339
+ html += '</tr>';
 
340
  });
341
+
342
+ html += '</tbody></table></div>';
343
+ if (t.annotation) html += `<p class="section-annot">${t.annotation}</p>`;
344
+
345
+ section.innerHTML = html;
346
+ container.appendChild(section);
347
  });
348
  </script>
349