eigen-db 4.2.0 → 4.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/README.md +55 -8
- package/dist/compute.d.ts +20 -0
- package/dist/eigen-db.js +242 -190
- package/dist/eigen-db.js.map +1 -1
- package/dist/eigen-db.umd.cjs +1 -1
- package/dist/eigen-db.umd.cjs.map +1 -1
- package/dist/errors.d.ts +7 -0
- package/dist/index.d.ts +12 -0
- package/dist/lexicon.d.ts +28 -0
- package/dist/memory-manager.d.ts +68 -0
- package/dist/result-set.d.ts +35 -0
- package/dist/simd-binary.d.ts +1 -0
- package/dist/storage.d.ts +38 -0
- package/dist/types.d.ts +44 -0
- package/dist/vector-db.d.ts +131 -0
- package/dist/wasm-compute.d.ts +13 -0
- package/package.json +4 -4
- package/src/lib/__tests__/vector-db.test.ts +328 -0
- package/src/lib/simd-binary.ts +1 -1
- package/src/lib/simd-optimized.wat +362 -0
- package/src/lib/simd.wat +42 -248
- package/src/lib/types.ts +2 -4
- package/src/lib/vector-db.ts +93 -19
package/src/lib/simd.wat
CHANGED
|
@@ -4,70 +4,38 @@
|
|
|
4
4
|
|
|
5
5
|
;; normalize(ptr: i32, dimensions: i32)
|
|
6
6
|
;; Normalizes a vector in-place to unit length using SIMD.
|
|
7
|
-
;; Optimized with 4x loop unrolling (16 floats/iteration) and multiple accumulators.
|
|
8
7
|
(func (export "normalize") (param $ptr i32) (param $dim i32)
|
|
9
8
|
(local $i i32)
|
|
10
|
-
(local $
|
|
11
|
-
(local $acc1 v128)
|
|
12
|
-
(local $acc2 v128)
|
|
13
|
-
(local $acc3 v128)
|
|
9
|
+
(local $acc v128)
|
|
14
10
|
(local $sum f32)
|
|
15
11
|
(local $mag f32)
|
|
16
12
|
(local $inv_mag f32)
|
|
17
13
|
(local $inv_vec v128)
|
|
18
|
-
(local $unroll_end i32)
|
|
19
14
|
(local $simd_end i32)
|
|
20
15
|
(local $offset i32)
|
|
21
16
|
|
|
22
|
-
;; Phase 1: Sum of squares
|
|
23
|
-
(local.set $
|
|
24
|
-
(local.set $acc1 (v128.const f32x4 0 0 0 0))
|
|
25
|
-
(local.set $acc2 (v128.const f32x4 0 0 0 0))
|
|
26
|
-
(local.set $acc3 (v128.const f32x4 0 0 0 0))
|
|
27
|
-
(local.set $unroll_end (i32.and (local.get $dim) (i32.const -16)))
|
|
17
|
+
;; Phase 1: Sum of squares
|
|
18
|
+
(local.set $acc (v128.const f32x4 0 0 0 0))
|
|
28
19
|
(local.set $simd_end (i32.and (local.get $dim) (i32.const -4)))
|
|
29
20
|
(local.set $i (i32.const 0))
|
|
30
21
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
22
|
+
;; SIMD loop: 4 floats per iteration
|
|
23
|
+
(block $break_sum
|
|
24
|
+
(loop $loop_sum
|
|
25
|
+
(br_if $break_sum (i32.ge_u (local.get $i) (local.get $simd_end)))
|
|
34
26
|
(local.set $offset (i32.add (local.get $ptr) (i32.shl (local.get $i) (i32.const 2))))
|
|
35
|
-
|
|
36
|
-
(local.set $acc0 (f32x4.add (local.get $acc0)
|
|
37
|
-
(f32x4.mul (v128.load (local.get $offset)) (v128.load (local.get $offset)))))
|
|
38
|
-
(local.set $acc1 (f32x4.add (local.get $acc1)
|
|
39
|
-
(f32x4.mul (v128.load offset=16 (local.get $offset)) (v128.load offset=16 (local.get $offset)))))
|
|
40
|
-
(local.set $acc2 (f32x4.add (local.get $acc2)
|
|
41
|
-
(f32x4.mul (v128.load offset=32 (local.get $offset)) (v128.load offset=32 (local.get $offset)))))
|
|
42
|
-
(local.set $acc3 (f32x4.add (local.get $acc3)
|
|
43
|
-
(f32x4.mul (v128.load offset=48 (local.get $offset)) (v128.load offset=48 (local.get $offset)))))
|
|
44
|
-
|
|
45
|
-
(local.set $i (i32.add (local.get $i) (i32.const 16)))
|
|
46
|
-
(br $loop_sum_u)
|
|
47
|
-
)
|
|
48
|
-
)
|
|
49
|
-
|
|
50
|
-
;; Merge 4 accumulators
|
|
51
|
-
(local.set $acc0 (f32x4.add (f32x4.add (local.get $acc0) (local.get $acc1))
|
|
52
|
-
(f32x4.add (local.get $acc2) (local.get $acc3))))
|
|
53
|
-
|
|
54
|
-
;; Remaining 4-wide chunks
|
|
55
|
-
(block $break_sum4
|
|
56
|
-
(loop $loop_sum4
|
|
57
|
-
(br_if $break_sum4 (i32.ge_u (local.get $i) (local.get $simd_end)))
|
|
58
|
-
(local.set $offset (i32.add (local.get $ptr) (i32.shl (local.get $i) (i32.const 2))))
|
|
59
|
-
(local.set $acc0 (f32x4.add (local.get $acc0)
|
|
27
|
+
(local.set $acc (f32x4.add (local.get $acc)
|
|
60
28
|
(f32x4.mul (v128.load (local.get $offset)) (v128.load (local.get $offset)))))
|
|
61
29
|
(local.set $i (i32.add (local.get $i) (i32.const 4)))
|
|
62
|
-
(br $
|
|
30
|
+
(br $loop_sum)
|
|
63
31
|
)
|
|
64
32
|
)
|
|
65
33
|
|
|
66
34
|
;; Horizontal sum
|
|
67
35
|
(local.set $sum
|
|
68
36
|
(f32.add
|
|
69
|
-
(f32.add (f32x4.extract_lane 0 (local.get $
|
|
70
|
-
(f32.add (f32x4.extract_lane 2 (local.get $
|
|
37
|
+
(f32.add (f32x4.extract_lane 0 (local.get $acc)) (f32x4.extract_lane 1 (local.get $acc)))
|
|
38
|
+
(f32.add (f32x4.extract_lane 2 (local.get $acc)) (f32x4.extract_lane 3 (local.get $acc)))))
|
|
71
39
|
|
|
72
40
|
;; Scalar remainder
|
|
73
41
|
(block $break_rem_sum
|
|
@@ -86,39 +54,20 @@
|
|
|
86
54
|
(if (f32.eq (local.get $mag) (f32.const 0))
|
|
87
55
|
(then (return)))
|
|
88
56
|
|
|
89
|
-
;; Phase 2: Scale by inverse magnitude
|
|
57
|
+
;; Phase 2: Scale by inverse magnitude
|
|
90
58
|
(local.set $inv_mag (f32.div (f32.const 1) (local.get $mag)))
|
|
91
59
|
(local.set $inv_vec (f32x4.splat (local.get $inv_mag)))
|
|
92
60
|
(local.set $i (i32.const 0))
|
|
93
61
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
(
|
|
98
|
-
|
|
99
|
-
(v128.store (local.get $offset)
|
|
100
|
-
(f32x4.mul (v128.load (local.get $offset)) (local.get $inv_vec)))
|
|
101
|
-
(v128.store offset=16 (local.get $offset)
|
|
102
|
-
(f32x4.mul (v128.load offset=16 (local.get $offset)) (local.get $inv_vec)))
|
|
103
|
-
(v128.store offset=32 (local.get $offset)
|
|
104
|
-
(f32x4.mul (v128.load offset=32 (local.get $offset)) (local.get $inv_vec)))
|
|
105
|
-
(v128.store offset=48 (local.get $offset)
|
|
106
|
-
(f32x4.mul (v128.load offset=48 (local.get $offset)) (local.get $inv_vec)))
|
|
107
|
-
|
|
108
|
-
(local.set $i (i32.add (local.get $i) (i32.const 16)))
|
|
109
|
-
(br $loop_norm_u)
|
|
110
|
-
)
|
|
111
|
-
)
|
|
112
|
-
|
|
113
|
-
;; Remaining 4-wide chunks
|
|
114
|
-
(block $break_norm4
|
|
115
|
-
(loop $loop_norm4
|
|
116
|
-
(br_if $break_norm4 (i32.ge_u (local.get $i) (local.get $simd_end)))
|
|
62
|
+
;; SIMD loop
|
|
63
|
+
(block $break_norm
|
|
64
|
+
(loop $loop_norm
|
|
65
|
+
(br_if $break_norm (i32.ge_u (local.get $i) (local.get $simd_end)))
|
|
117
66
|
(local.set $offset (i32.add (local.get $ptr) (i32.shl (local.get $i) (i32.const 2))))
|
|
118
67
|
(v128.store (local.get $offset)
|
|
119
68
|
(f32x4.mul (v128.load (local.get $offset)) (local.get $inv_vec)))
|
|
120
69
|
(local.set $i (i32.add (local.get $i) (i32.const 4)))
|
|
121
|
-
(br $
|
|
70
|
+
(br $loop_norm)
|
|
122
71
|
)
|
|
123
72
|
)
|
|
124
73
|
|
|
@@ -137,226 +86,71 @@
|
|
|
137
86
|
|
|
138
87
|
;; search_all(query_ptr, db_ptr, scores_ptr, db_size, dimensions)
|
|
139
88
|
;; Computes dot products of query against every vector in the database.
|
|
140
|
-
;; Optimized with:
|
|
141
|
-
;; - 2-vector outer loop unrolling (halves query memory reads)
|
|
142
|
-
;; - 4x inner loop unrolling (16 floats/iteration, 4 accumulators per vector)
|
|
143
89
|
(func (export "search_all") (param $query_ptr i32) (param $db_ptr i32) (param $scores_ptr i32) (param $db_size i32) (param $dim i32)
|
|
144
90
|
(local $i i32)
|
|
145
91
|
(local $j i32)
|
|
146
|
-
(local $
|
|
147
|
-
(local $
|
|
148
|
-
(local $
|
|
149
|
-
(local $accA3 v128)
|
|
150
|
-
(local $accB0 v128)
|
|
151
|
-
(local $accB1 v128)
|
|
152
|
-
(local $accB2 v128)
|
|
153
|
-
(local $accB3 v128)
|
|
154
|
-
(local $q0 v128)
|
|
155
|
-
(local $q1 v128)
|
|
156
|
-
(local $q2 v128)
|
|
157
|
-
(local $q3 v128)
|
|
158
|
-
(local $dotA f32)
|
|
159
|
-
(local $dotB f32)
|
|
160
|
-
(local $vec_ptrA i32)
|
|
161
|
-
(local $vec_ptrB i32)
|
|
162
|
-
(local $unroll_end i32)
|
|
92
|
+
(local $acc v128)
|
|
93
|
+
(local $dot f32)
|
|
94
|
+
(local $vec_ptr i32)
|
|
163
95
|
(local $simd_end i32)
|
|
164
96
|
(local $q_offset i32)
|
|
165
|
-
(local $
|
|
166
|
-
(local $vB_offset i32)
|
|
97
|
+
(local $v_offset i32)
|
|
167
98
|
(local $bytes_per_vec i32)
|
|
168
|
-
(local $pair_end i32)
|
|
169
99
|
|
|
170
|
-
(local.set $unroll_end (i32.and (local.get $dim) (i32.const -16)))
|
|
171
100
|
(local.set $simd_end (i32.and (local.get $dim) (i32.const -4)))
|
|
172
101
|
(local.set $bytes_per_vec (i32.shl (local.get $dim) (i32.const 2)))
|
|
173
|
-
(local.set $pair_end (i32.and (local.get $db_size) (i32.const -2)))
|
|
174
102
|
(local.set $i (i32.const 0))
|
|
175
103
|
|
|
176
|
-
;;
|
|
104
|
+
;; Outer loop: one database vector per iteration
|
|
177
105
|
(block $break_outer
|
|
178
106
|
(loop $loop_outer
|
|
179
|
-
(br_if $break_outer (i32.ge_u (local.get $i) (local.get $
|
|
107
|
+
(br_if $break_outer (i32.ge_u (local.get $i) (local.get $db_size)))
|
|
180
108
|
|
|
181
|
-
(local.set $
|
|
109
|
+
(local.set $vec_ptr
|
|
182
110
|
(i32.add (local.get $db_ptr) (i32.mul (local.get $i) (local.get $bytes_per_vec))))
|
|
183
|
-
(local.set $
|
|
184
|
-
(i32.add (local.get $vec_ptrA) (local.get $bytes_per_vec)))
|
|
185
|
-
|
|
186
|
-
(local.set $accA0 (v128.const f32x4 0 0 0 0))
|
|
187
|
-
(local.set $accA1 (v128.const f32x4 0 0 0 0))
|
|
188
|
-
(local.set $accA2 (v128.const f32x4 0 0 0 0))
|
|
189
|
-
(local.set $accA3 (v128.const f32x4 0 0 0 0))
|
|
190
|
-
(local.set $accB0 (v128.const f32x4 0 0 0 0))
|
|
191
|
-
(local.set $accB1 (v128.const f32x4 0 0 0 0))
|
|
192
|
-
(local.set $accB2 (v128.const f32x4 0 0 0 0))
|
|
193
|
-
(local.set $accB3 (v128.const f32x4 0 0 0 0))
|
|
111
|
+
(local.set $acc (v128.const f32x4 0 0 0 0))
|
|
194
112
|
(local.set $j (i32.const 0))
|
|
195
113
|
|
|
196
|
-
;;
|
|
114
|
+
;; SIMD inner loop: dot product, 4 floats per iteration
|
|
197
115
|
(block $break_inner
|
|
198
116
|
(loop $loop_inner
|
|
199
|
-
(br_if $break_inner (i32.ge_u (local.get $j) (local.get $
|
|
200
|
-
(local.set $q_offset (i32.add (local.get $query_ptr) (i32.shl (local.get $j) (i32.const 2))))
|
|
201
|
-
(local.set $vA_offset (i32.add (local.get $vec_ptrA) (i32.shl (local.get $j) (i32.const 2))))
|
|
202
|
-
(local.set $vB_offset (i32.add (local.get $vec_ptrB) (i32.shl (local.get $j) (i32.const 2))))
|
|
203
|
-
|
|
204
|
-
;; Load query chunk (shared between both vectors)
|
|
205
|
-
(local.set $q0 (v128.load (local.get $q_offset)))
|
|
206
|
-
(local.set $q1 (v128.load offset=16 (local.get $q_offset)))
|
|
207
|
-
(local.set $q2 (v128.load offset=32 (local.get $q_offset)))
|
|
208
|
-
(local.set $q3 (v128.load offset=48 (local.get $q_offset)))
|
|
209
|
-
|
|
210
|
-
;; Vector A
|
|
211
|
-
(local.set $accA0 (f32x4.add (local.get $accA0)
|
|
212
|
-
(f32x4.mul (local.get $q0) (v128.load (local.get $vA_offset)))))
|
|
213
|
-
(local.set $accA1 (f32x4.add (local.get $accA1)
|
|
214
|
-
(f32x4.mul (local.get $q1) (v128.load offset=16 (local.get $vA_offset)))))
|
|
215
|
-
(local.set $accA2 (f32x4.add (local.get $accA2)
|
|
216
|
-
(f32x4.mul (local.get $q2) (v128.load offset=32 (local.get $vA_offset)))))
|
|
217
|
-
(local.set $accA3 (f32x4.add (local.get $accA3)
|
|
218
|
-
(f32x4.mul (local.get $q3) (v128.load offset=48 (local.get $vA_offset)))))
|
|
219
|
-
|
|
220
|
-
;; Vector B (reuses query loads)
|
|
221
|
-
(local.set $accB0 (f32x4.add (local.get $accB0)
|
|
222
|
-
(f32x4.mul (local.get $q0) (v128.load (local.get $vB_offset)))))
|
|
223
|
-
(local.set $accB1 (f32x4.add (local.get $accB1)
|
|
224
|
-
(f32x4.mul (local.get $q1) (v128.load offset=16 (local.get $vB_offset)))))
|
|
225
|
-
(local.set $accB2 (f32x4.add (local.get $accB2)
|
|
226
|
-
(f32x4.mul (local.get $q2) (v128.load offset=32 (local.get $vB_offset)))))
|
|
227
|
-
(local.set $accB3 (f32x4.add (local.get $accB3)
|
|
228
|
-
(f32x4.mul (local.get $q3) (v128.load offset=48 (local.get $vB_offset)))))
|
|
229
|
-
|
|
230
|
-
(local.set $j (i32.add (local.get $j) (i32.const 16)))
|
|
231
|
-
(br $loop_inner)
|
|
232
|
-
)
|
|
233
|
-
)
|
|
234
|
-
|
|
235
|
-
;; Merge accumulators
|
|
236
|
-
(local.set $accA0 (f32x4.add (f32x4.add (local.get $accA0) (local.get $accA1))
|
|
237
|
-
(f32x4.add (local.get $accA2) (local.get $accA3))))
|
|
238
|
-
(local.set $accB0 (f32x4.add (f32x4.add (local.get $accB0) (local.get $accB1))
|
|
239
|
-
(f32x4.add (local.get $accB2) (local.get $accB3))))
|
|
240
|
-
|
|
241
|
-
;; 4-wide cleanup (both vectors)
|
|
242
|
-
(block $break_inner4
|
|
243
|
-
(loop $loop_inner4
|
|
244
|
-
(br_if $break_inner4 (i32.ge_u (local.get $j) (local.get $simd_end)))
|
|
117
|
+
(br_if $break_inner (i32.ge_u (local.get $j) (local.get $simd_end)))
|
|
245
118
|
(local.set $q_offset (i32.add (local.get $query_ptr) (i32.shl (local.get $j) (i32.const 2))))
|
|
246
|
-
(local.set $
|
|
247
|
-
(local.set $
|
|
248
|
-
|
|
249
|
-
(local.set $accA0 (f32x4.add (local.get $accA0) (f32x4.mul (local.get $q0) (v128.load (local.get $vA_offset)))))
|
|
250
|
-
(local.set $accB0 (f32x4.add (local.get $accB0) (f32x4.mul (local.get $q0) (v128.load (local.get $vB_offset)))))
|
|
119
|
+
(local.set $v_offset (i32.add (local.get $vec_ptr) (i32.shl (local.get $j) (i32.const 2))))
|
|
120
|
+
(local.set $acc (f32x4.add (local.get $acc)
|
|
121
|
+
(f32x4.mul (v128.load (local.get $q_offset)) (v128.load (local.get $v_offset)))))
|
|
251
122
|
(local.set $j (i32.add (local.get $j) (i32.const 4)))
|
|
252
|
-
(br $
|
|
123
|
+
(br $loop_inner)
|
|
253
124
|
)
|
|
254
125
|
)
|
|
255
126
|
|
|
256
|
-
;; Horizontal
|
|
257
|
-
(local.set $
|
|
127
|
+
;; Horizontal sum
|
|
128
|
+
(local.set $dot
|
|
258
129
|
(f32.add
|
|
259
|
-
(f32.add (f32x4.extract_lane 0 (local.get $
|
|
260
|
-
(f32.add (f32x4.extract_lane 2 (local.get $
|
|
261
|
-
(local.set $dotB
|
|
262
|
-
(f32.add
|
|
263
|
-
(f32.add (f32x4.extract_lane 0 (local.get $accB0)) (f32x4.extract_lane 1 (local.get $accB0)))
|
|
264
|
-
(f32.add (f32x4.extract_lane 2 (local.get $accB0)) (f32x4.extract_lane 3 (local.get $accB0)))))
|
|
130
|
+
(f32.add (f32x4.extract_lane 0 (local.get $acc)) (f32x4.extract_lane 1 (local.get $acc)))
|
|
131
|
+
(f32.add (f32x4.extract_lane 2 (local.get $acc)) (f32x4.extract_lane 3 (local.get $acc)))))
|
|
265
132
|
|
|
266
|
-
;; Scalar remainder
|
|
133
|
+
;; Scalar remainder
|
|
267
134
|
(block $break_rem
|
|
268
135
|
(loop $loop_rem
|
|
269
136
|
(br_if $break_rem (i32.ge_u (local.get $j) (local.get $dim)))
|
|
270
137
|
(local.set $q_offset (i32.add (local.get $query_ptr) (i32.shl (local.get $j) (i32.const 2))))
|
|
271
|
-
(local.set $
|
|
272
|
-
(local.set $
|
|
273
|
-
|
|
274
|
-
(f32.mul (f32.load (local.get $q_offset)) (f32.load (local.get $vA_offset)))))
|
|
275
|
-
(local.set $dotB (f32.add (local.get $dotB)
|
|
276
|
-
(f32.mul (f32.load (local.get $q_offset)) (f32.load (local.get $vB_offset)))))
|
|
138
|
+
(local.set $v_offset (i32.add (local.get $vec_ptr) (i32.shl (local.get $j) (i32.const 2))))
|
|
139
|
+
(local.set $dot (f32.add (local.get $dot)
|
|
140
|
+
(f32.mul (f32.load (local.get $q_offset)) (f32.load (local.get $v_offset)))))
|
|
277
141
|
(local.set $j (i32.add (local.get $j) (i32.const 1)))
|
|
278
142
|
(br $loop_rem)
|
|
279
143
|
)
|
|
280
144
|
)
|
|
281
145
|
|
|
282
|
-
;; Store
|
|
146
|
+
;; Store score
|
|
283
147
|
(f32.store
|
|
284
148
|
(i32.add (local.get $scores_ptr) (i32.shl (local.get $i) (i32.const 2)))
|
|
285
|
-
(local.get $
|
|
286
|
-
(f32.store
|
|
287
|
-
(i32.add (local.get $scores_ptr) (i32.shl (i32.add (local.get $i) (i32.const 1)) (i32.const 2)))
|
|
288
|
-
(local.get $dotB))
|
|
149
|
+
(local.get $dot))
|
|
289
150
|
|
|
290
|
-
(local.set $i (i32.add (local.get $i) (i32.const
|
|
151
|
+
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
|
291
152
|
(br $loop_outer)
|
|
292
153
|
)
|
|
293
154
|
)
|
|
294
|
-
|
|
295
|
-
;; Handle last vector if db_size is odd
|
|
296
|
-
(if (i32.lt_u (local.get $i) (local.get $db_size))
|
|
297
|
-
(then
|
|
298
|
-
(local.set $vec_ptrA
|
|
299
|
-
(i32.add (local.get $db_ptr) (i32.mul (local.get $i) (local.get $bytes_per_vec))))
|
|
300
|
-
(local.set $accA0 (v128.const f32x4 0 0 0 0))
|
|
301
|
-
(local.set $accA1 (v128.const f32x4 0 0 0 0))
|
|
302
|
-
(local.set $accA2 (v128.const f32x4 0 0 0 0))
|
|
303
|
-
(local.set $accA3 (v128.const f32x4 0 0 0 0))
|
|
304
|
-
(local.set $j (i32.const 0))
|
|
305
|
-
|
|
306
|
-
(block $break_last
|
|
307
|
-
(loop $loop_last
|
|
308
|
-
(br_if $break_last (i32.ge_u (local.get $j) (local.get $unroll_end)))
|
|
309
|
-
(local.set $q_offset (i32.add (local.get $query_ptr) (i32.shl (local.get $j) (i32.const 2))))
|
|
310
|
-
(local.set $vA_offset (i32.add (local.get $vec_ptrA) (i32.shl (local.get $j) (i32.const 2))))
|
|
311
|
-
(local.set $accA0 (f32x4.add (local.get $accA0)
|
|
312
|
-
(f32x4.mul (v128.load (local.get $q_offset)) (v128.load (local.get $vA_offset)))))
|
|
313
|
-
(local.set $accA1 (f32x4.add (local.get $accA1)
|
|
314
|
-
(f32x4.mul (v128.load offset=16 (local.get $q_offset)) (v128.load offset=16 (local.get $vA_offset)))))
|
|
315
|
-
(local.set $accA2 (f32x4.add (local.get $accA2)
|
|
316
|
-
(f32x4.mul (v128.load offset=32 (local.get $q_offset)) (v128.load offset=32 (local.get $vA_offset)))))
|
|
317
|
-
(local.set $accA3 (f32x4.add (local.get $accA3)
|
|
318
|
-
(f32x4.mul (v128.load offset=48 (local.get $q_offset)) (v128.load offset=48 (local.get $vA_offset)))))
|
|
319
|
-
(local.set $j (i32.add (local.get $j) (i32.const 16)))
|
|
320
|
-
(br $loop_last)
|
|
321
|
-
)
|
|
322
|
-
)
|
|
323
|
-
|
|
324
|
-
(local.set $accA0 (f32x4.add (f32x4.add (local.get $accA0) (local.get $accA1))
|
|
325
|
-
(f32x4.add (local.get $accA2) (local.get $accA3))))
|
|
326
|
-
|
|
327
|
-
(block $break_last4
|
|
328
|
-
(loop $loop_last4
|
|
329
|
-
(br_if $break_last4 (i32.ge_u (local.get $j) (local.get $simd_end)))
|
|
330
|
-
(local.set $q_offset (i32.add (local.get $query_ptr) (i32.shl (local.get $j) (i32.const 2))))
|
|
331
|
-
(local.set $vA_offset (i32.add (local.get $vec_ptrA) (i32.shl (local.get $j) (i32.const 2))))
|
|
332
|
-
(local.set $accA0 (f32x4.add (local.get $accA0)
|
|
333
|
-
(f32x4.mul (v128.load (local.get $q_offset)) (v128.load (local.get $vA_offset)))))
|
|
334
|
-
(local.set $j (i32.add (local.get $j) (i32.const 4)))
|
|
335
|
-
(br $loop_last4)
|
|
336
|
-
)
|
|
337
|
-
)
|
|
338
|
-
|
|
339
|
-
(local.set $dotA
|
|
340
|
-
(f32.add
|
|
341
|
-
(f32.add (f32x4.extract_lane 0 (local.get $accA0)) (f32x4.extract_lane 1 (local.get $accA0)))
|
|
342
|
-
(f32.add (f32x4.extract_lane 2 (local.get $accA0)) (f32x4.extract_lane 3 (local.get $accA0)))))
|
|
343
|
-
|
|
344
|
-
(block $break_last_rem
|
|
345
|
-
(loop $loop_last_rem
|
|
346
|
-
(br_if $break_last_rem (i32.ge_u (local.get $j) (local.get $dim)))
|
|
347
|
-
(local.set $q_offset (i32.add (local.get $query_ptr) (i32.shl (local.get $j) (i32.const 2))))
|
|
348
|
-
(local.set $vA_offset (i32.add (local.get $vec_ptrA) (i32.shl (local.get $j) (i32.const 2))))
|
|
349
|
-
(local.set $dotA (f32.add (local.get $dotA)
|
|
350
|
-
(f32.mul (f32.load (local.get $q_offset)) (f32.load (local.get $vA_offset)))))
|
|
351
|
-
(local.set $j (i32.add (local.get $j) (i32.const 1)))
|
|
352
|
-
(br $loop_last_rem)
|
|
353
|
-
)
|
|
354
|
-
)
|
|
355
|
-
|
|
356
|
-
(f32.store
|
|
357
|
-
(i32.add (local.get $scores_ptr) (i32.shl (local.get $i) (i32.const 2)))
|
|
358
|
-
(local.get $dotA))
|
|
359
|
-
)
|
|
360
|
-
)
|
|
361
155
|
)
|
|
362
156
|
)
|
package/src/lib/types.ts
CHANGED
|
@@ -2,20 +2,18 @@
|
|
|
2
2
|
* Options for opening a VectorDB instance.
|
|
3
3
|
*/
|
|
4
4
|
export interface OpenOptions {
|
|
5
|
-
/** Name of the OPFS directory. Defaults to "default". */
|
|
6
|
-
name?: string;
|
|
7
5
|
/** Vector dimensions (e.g., 1536 for OpenAI text-embedding-3-small) */
|
|
8
6
|
dimensions: number;
|
|
9
7
|
/** Whether to normalize vectors on set/query (default: true). */
|
|
10
8
|
normalize?: boolean;
|
|
9
|
+
/** Storage provider for persistence. Defaults to InMemoryStorageProvider. Use OPFSStorageProvider for browser persistence. */
|
|
10
|
+
storage?: import("./storage").StorageProvider;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Extended options including internal overrides for testing/advanced use.
|
|
15
15
|
*/
|
|
16
16
|
export interface OpenOptionsInternal extends OpenOptions {
|
|
17
|
-
/** Override the storage provider (default: OPFS). Useful for testing. */
|
|
18
|
-
storage?: import("./storage").StorageProvider;
|
|
19
17
|
/** Pre-compiled WASM binary. If not provided, uses the embedded SIMD binary. Set to null to force JS-only mode. */
|
|
20
18
|
wasmBinary?: Uint8Array | null;
|
|
21
19
|
}
|
package/src/lib/vector-db.ts
CHANGED
|
@@ -20,7 +20,7 @@ import type { ResultItem } from "./result-set";
|
|
|
20
20
|
import { iterableResults, topKResults } from "./result-set";
|
|
21
21
|
import { getSimdWasmBinary } from "./simd-binary";
|
|
22
22
|
import type { StorageProvider } from "./storage";
|
|
23
|
-
import {
|
|
23
|
+
import { InMemoryStorageProvider } from "./storage";
|
|
24
24
|
import type { OpenOptions, OpenOptionsInternal, QueryOptions, SetOptions, VectorInput } from "./types";
|
|
25
25
|
import { instantiateWasm, type WasmExports } from "./wasm-compute";
|
|
26
26
|
|
|
@@ -38,7 +38,7 @@ const STREAM_CHUNK_SIZE = 65536;
|
|
|
38
38
|
export class VectorDB {
|
|
39
39
|
private readonly memoryManager: MemoryManager;
|
|
40
40
|
private readonly storage: StorageProvider;
|
|
41
|
-
private readonly
|
|
41
|
+
private readonly _dimensions: number;
|
|
42
42
|
private readonly shouldNormalize: boolean;
|
|
43
43
|
private wasmExports: WasmExports | null;
|
|
44
44
|
|
|
@@ -62,7 +62,7 @@ export class VectorDB {
|
|
|
62
62
|
) {
|
|
63
63
|
this.memoryManager = memoryManager;
|
|
64
64
|
this.storage = storage;
|
|
65
|
-
this.
|
|
65
|
+
this._dimensions = dimensions;
|
|
66
66
|
this.shouldNormalize = shouldNormalize;
|
|
67
67
|
this.wasmExports = wasmExports;
|
|
68
68
|
this.keyToSlot = keyToSlot;
|
|
@@ -76,8 +76,7 @@ export class VectorDB {
|
|
|
76
76
|
static async open(options: OpenOptions): Promise<VectorDB>;
|
|
77
77
|
static async open(options: OpenOptionsInternal): Promise<VectorDB>;
|
|
78
78
|
static async open(options: OpenOptionsInternal): Promise<VectorDB> {
|
|
79
|
-
const
|
|
80
|
-
const storage = options.storage ?? new OPFSStorageProvider(name);
|
|
79
|
+
const storage = options.storage ?? new InMemoryStorageProvider();
|
|
81
80
|
const shouldNormalize = options.normalize !== false;
|
|
82
81
|
|
|
83
82
|
// Load existing data from storage
|
|
@@ -123,6 +122,81 @@ export class VectorDB {
|
|
|
123
122
|
return this.keyToSlot.size;
|
|
124
123
|
}
|
|
125
124
|
|
|
125
|
+
/** Number of dimensions per vector */
|
|
126
|
+
get dimensions(): number {
|
|
127
|
+
return this._dimensions;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Check whether a key exists in the database.
|
|
132
|
+
* Uses the internal key-to-slot map for O(1) lookup.
|
|
133
|
+
*/
|
|
134
|
+
has(key: string): boolean {
|
|
135
|
+
this.assertOpen();
|
|
136
|
+
return this.keyToSlot.has(key);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Delete an entry by key. Returns true if the key existed, false otherwise.
|
|
141
|
+
* Uses swap-and-pop to avoid gaps in the underlying vector array.
|
|
142
|
+
*/
|
|
143
|
+
delete(key: string): boolean {
|
|
144
|
+
this.assertOpen();
|
|
145
|
+
|
|
146
|
+
const slot = this.keyToSlot.get(key);
|
|
147
|
+
if (slot === undefined) return false;
|
|
148
|
+
|
|
149
|
+
const lastSlot = this.memoryManager.vectorCount - 1;
|
|
150
|
+
|
|
151
|
+
if (slot !== lastSlot) {
|
|
152
|
+
// Move last vector into the deleted slot
|
|
153
|
+
const lastVector = new Float32Array(this.memoryManager.readVector(lastSlot));
|
|
154
|
+
this.memoryManager.writeVector(slot, lastVector);
|
|
155
|
+
|
|
156
|
+
// Update mappings for the moved key
|
|
157
|
+
const movedKey = this.slotToKey[lastSlot];
|
|
158
|
+
this.keyToSlot.set(movedKey, slot);
|
|
159
|
+
this.slotToKey[slot] = movedKey;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Remove the deleted key and shrink
|
|
163
|
+
this.keyToSlot.delete(key);
|
|
164
|
+
this.slotToKey.length = lastSlot;
|
|
165
|
+
this.memoryManager.setVectorCount(lastSlot);
|
|
166
|
+
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Returns an iterable of all keys in the database.
|
|
172
|
+
*/
|
|
173
|
+
keys(): IterableIterator<string> {
|
|
174
|
+
this.assertOpen();
|
|
175
|
+
return this.keyToSlot.keys();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Returns an iterable of [key, value] pairs.
|
|
180
|
+
* Values are returned as plain number array copies.
|
|
181
|
+
*/
|
|
182
|
+
entries(): IterableIterator<[string, number[]]> {
|
|
183
|
+
this.assertOpen();
|
|
184
|
+
const keyToSlot = this.keyToSlot;
|
|
185
|
+
const mm = this.memoryManager;
|
|
186
|
+
return (function* () {
|
|
187
|
+
for (const [key, slot] of keyToSlot) {
|
|
188
|
+
yield [key, Array.from(mm.readVector(slot))] as [string, number[]];
|
|
189
|
+
}
|
|
190
|
+
})();
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Implements the iterable protocol. Same as entries().
|
|
195
|
+
*/
|
|
196
|
+
[Symbol.iterator](): IterableIterator<[string, number[]]> {
|
|
197
|
+
return this.entries();
|
|
198
|
+
}
|
|
199
|
+
|
|
126
200
|
/**
|
|
127
201
|
* Set a key-value pair. If the key already exists, its vector is overwritten (last-write-wins).
|
|
128
202
|
* The value is a number[] or Float32Array of length equal to the configured dimensions.
|
|
@@ -130,8 +204,8 @@ export class VectorDB {
|
|
|
130
204
|
set(key: string, value: VectorInput, options?: SetOptions): void {
|
|
131
205
|
this.assertOpen();
|
|
132
206
|
|
|
133
|
-
if (value.length !== this.
|
|
134
|
-
throw new Error(`Vector dimension mismatch: expected ${this.
|
|
207
|
+
if (value.length !== this._dimensions) {
|
|
208
|
+
throw new Error(`Vector dimension mismatch: expected ${this._dimensions}, got ${value.length}`);
|
|
135
209
|
}
|
|
136
210
|
|
|
137
211
|
// Convert to Float32Array (also clones to avoid mutating caller's array)
|
|
@@ -215,8 +289,8 @@ export class VectorDB {
|
|
|
215
289
|
return [];
|
|
216
290
|
}
|
|
217
291
|
|
|
218
|
-
if (value.length !== this.
|
|
219
|
-
throw new Error(`Query vector dimension mismatch: expected ${this.
|
|
292
|
+
if (value.length !== this._dimensions) {
|
|
293
|
+
throw new Error(`Query vector dimension mismatch: expected ${this._dimensions}, got ${value.length}`);
|
|
220
294
|
}
|
|
221
295
|
|
|
222
296
|
// Convert to Float32Array and optionally normalize the query vector
|
|
@@ -243,21 +317,21 @@ export class VectorDB {
|
|
|
243
317
|
this.memoryManager.dbOffset,
|
|
244
318
|
scoresOffset,
|
|
245
319
|
totalVectors,
|
|
246
|
-
this.
|
|
320
|
+
this._dimensions,
|
|
247
321
|
);
|
|
248
322
|
} else {
|
|
249
323
|
const queryView = new Float32Array(
|
|
250
324
|
this.memoryManager.memory.buffer,
|
|
251
325
|
this.memoryManager.queryOffset,
|
|
252
|
-
this.
|
|
326
|
+
this._dimensions,
|
|
253
327
|
);
|
|
254
328
|
const dbView = new Float32Array(
|
|
255
329
|
this.memoryManager.memory.buffer,
|
|
256
330
|
this.memoryManager.dbOffset,
|
|
257
|
-
totalVectors * this.
|
|
331
|
+
totalVectors * this._dimensions,
|
|
258
332
|
);
|
|
259
333
|
const scoresView = new Float32Array(this.memoryManager.memory.buffer, scoresOffset, totalVectors);
|
|
260
|
-
searchAll(queryView, dbView, scoresView, totalVectors, this.
|
|
334
|
+
searchAll(queryView, dbView, scoresView, totalVectors, this._dimensions);
|
|
261
335
|
}
|
|
262
336
|
|
|
263
337
|
// Read scores (make a copy so the buffer can be reused)
|
|
@@ -284,12 +358,12 @@ export class VectorDB {
|
|
|
284
358
|
const totalVectors = this.memoryManager.vectorCount;
|
|
285
359
|
|
|
286
360
|
// Serialize vectors from WASM memory
|
|
287
|
-
const vectorBytes = new Uint8Array(totalVectors * this.
|
|
361
|
+
const vectorBytes = new Uint8Array(totalVectors * this._dimensions * 4);
|
|
288
362
|
if (totalVectors > 0) {
|
|
289
363
|
const src = new Uint8Array(
|
|
290
364
|
this.memoryManager.memory.buffer,
|
|
291
365
|
this.memoryManager.dbOffset,
|
|
292
|
-
totalVectors * this.
|
|
366
|
+
totalVectors * this._dimensions * 4,
|
|
293
367
|
);
|
|
294
368
|
vectorBytes.set(src);
|
|
295
369
|
}
|
|
@@ -336,7 +410,7 @@ export class VectorDB {
|
|
|
336
410
|
this.assertOpen();
|
|
337
411
|
|
|
338
412
|
const totalVectors = this.memoryManager.vectorCount;
|
|
339
|
-
const vectorDataLen = totalVectors * this.
|
|
413
|
+
const vectorDataLen = totalVectors * this._dimensions * 4;
|
|
340
414
|
|
|
341
415
|
// Encode keys (typically much smaller than vectors)
|
|
342
416
|
const keysBytes = encodeLexicon(this.slotToKey);
|
|
@@ -347,7 +421,7 @@ export class VectorDB {
|
|
|
347
421
|
const headerView = new DataView(header);
|
|
348
422
|
headerView.setUint32(0, EXPORT_MAGIC, true);
|
|
349
423
|
headerView.setUint32(4, EXPORT_VERSION, true);
|
|
350
|
-
headerView.setUint32(8, this.
|
|
424
|
+
headerView.setUint32(8, this._dimensions, true);
|
|
351
425
|
headerView.setUint32(12, totalVectors, true);
|
|
352
426
|
headerView.setUint32(16, vectorDataLen, true);
|
|
353
427
|
headerView.setUint32(20, keysDataLen, true);
|
|
@@ -416,8 +490,8 @@ export class VectorDB {
|
|
|
416
490
|
}
|
|
417
491
|
|
|
418
492
|
const dimensions = headerView.getUint32(8, true);
|
|
419
|
-
if (dimensions !== this.
|
|
420
|
-
throw new Error(`Import dimension mismatch: expected ${this.
|
|
493
|
+
if (dimensions !== this._dimensions) {
|
|
494
|
+
throw new Error(`Import dimension mismatch: expected ${this._dimensions}, got ${dimensions}`);
|
|
421
495
|
}
|
|
422
496
|
|
|
423
497
|
const vectorCount = headerView.getUint32(12, true);
|