Repository navigation
Expand file tree
/
Copy path12-transformers-embedder.html
More file actions
117 lines (103 loc) · 5.31 KB
/
Copy path12-transformers-embedder.html
File metadata and controls
117 lines (103 loc) · 5.31 KB
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
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>BrowserVec — 12. Transformers.js embedder</title>
<!--
transformersEmbedder() loads @xenova/transformers via a non-literal,
@vite-ignore'd dynamic import (src/embed/transformers.ts) so neither the
library nor the model land in BrowserVec's core bundle. The tradeoff:
browsers can't resolve a bare specifier like '@xenova/transformers' from a
dynamic import() on their own — @vite-ignore skips Vite's rewriting for this
call site even under the dev server — so an import map is required, and a
CDN URL (not a local node_modules path) is what actually resolves once this
page is deployed as a static site.
-->
<script type="importmap">
{ "imports": { "@xenova/transformers": "https://cdn.jsdelivr.net/npm/@xenova/transformers@2.17.2/+esm" } }
</script>
<style>.bv-nav{font-size:.78rem;margin:0 0 1rem;color:#888;}.bv-nav a{color:#888;text-decoration:none;}.bv-nav a:hover{color:#1a73e8;text-decoration:underline;}body{font:14px/1.5 ui-monospace,monospace;margin:2rem;max-width:780px}pre{background:#111;color:#b9f;padding:1rem;border-radius:6px;overflow:auto}button{font:inherit;padding:.4rem .8rem;cursor:pointer}.ok{color:#5c5}.bad{color:#f55}.dim{color:#888}.hl{color:#fe9}h2{font-size:1.1rem;border-bottom:1px solid #333;padding-bottom:.25rem}</style>
</head>
<body>
<p class="bv-nav"><a href="./">← All examples</a> · <a href="../">Home</a></p>
<h1>12. Transformers.js embedder (M5)</h1>
<p class="dim">Real <b>semantic</b> embeddings via <code>@xenova/transformers</code>. The model (<code>all-MiniLM-L6-v2</code>, 384-dim) downloads once and is cached by the browser — later sessions run offline. The library is <b>lazy-loaded</b> (code-split) so it's never in the core bundle if you don't use it. The vector store itself runs on CPU fallback (WASM-SIMD) if WebGPU is unavailable.</p>
<pre id="out">ready.</pre>
<button id="run">Run transformers embedder demo</button>
<script type="module">
import { BrowserVec, transformersEmbedder } from '../src/index.ts';
const $ = (id) => document.getElementById(id);
const out = $('out');
const log = (msg, cls = '') => (out.innerHTML += `\n${cls ? `<span class="${cls}">${msg}</span>` : msg}`);
$('run').onclick = async () => {
out.textContent = '';
log(`Loading transformers embedder (all-MiniLM-L6-v2, 384-dim)...`);
log(`(First load downloads ~90 MB model — subsequent loads are instant.)`, 'dim');
log(``);
let embedder;
try {
embedder = await transformersEmbedder({
dimension: 384,
model: 'Xenova/all-MiniLM-L6-v2',
onProgress: (info) => {
if (info?.status === 'progress') {
log(` downloading model: ${(info.loaded / (1024 * 1024)).toFixed(0)} / ${(info.total / (1024 * 1024)).toFixed(0)} MiB`, 'dim');
}
},
});
log(`embedder ready (dim=${embedder.dimension})`, 'ok');
} catch (e) {
log(`Failed to load transformers embedder: ${e.message}`, 'bad');
log(`Make sure @xenova/transformers is installed: npm install @xenova/transformers`, 'bad');
return;
}
const db = await BrowserVec.create({
dimension: 384,
metric: 'cosine',
embedder,
fallback: 'wasm',
});
if (db.stats().device !== 'webgpu') {
log('No WebGPU — running on CPU fallback (WASM-SIMD), exact fp32 results.', 'dim');
}
// Documents with semantically related content
const docs = [
{ id: 'd1', text: 'The cat sat on the mat and purred contentedly' },
{ id: 'd2', text: 'Dogs love to play fetch in the park with their owners' },
{ id: 'd3', text: 'WebGPU compute shaders run massively parallel operations on the GPU' },
{ id: 'd4', text: 'The capital of France is Paris, known for the Eiffel Tower' },
{ id: 'd5', text: 'Machine learning models can classify images and text' },
{ id: 'd6', text: 'Kittens are playful and enjoy chasing strings and toys' },
{ id: 'd7', text: 'Web development involves HTML, CSS, and JavaScript' },
{ id: 'd8', text: 'GPU programming enables high-performance scientific computing' },
];
log(`\n--- Ingesting ${docs.length} documents ---`);
let t = performance.now();
await db.addTexts(docs.map((d) => ({ ...d, metadata: { text: d.text } })));
log(`ingested ${db.count} docs in ${(performance.now() - t).toFixed(0)} ms`, 'ok');
// Semantic queries — transformers embedder understands meaning, not just keywords
const queries = [
'feline pets and their behavior',
'computer programming and web technologies',
'graphics processing unit computation',
];
log(`\n--- Semantic text queries (k=2) ---`);
for (const q of queries) {
const hits = await db.queryText(q, { k: 2 });
log(`\nquery: "${q}"`);
for (const h of hits) {
log(` ${h.score.toFixed(3)} ${h.metadata?.text}`, h.score > 0.5 ? 'ok' : 'dim');
}
}
// Compare with keyword-level hashingEmbedder to see the difference
log(`\n--- Contrast with keyword embedder ---`);
log(`The hashingEmbedder (keyword overlap) would fail on synonyms like`, 'dim');
log(`"feline" -> "cat" or "GPU" -> "graphics processing unit".`, 'dim');
log(`The transformers embedder understands meaning, not just word matches.`, 'ok');
db.destroy();
log(`\nDone. Model weights remain cached for the next session.`, 'dim');
};
</script>
</body>
</html>