sweet-search 0.0.1 → 2.3.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/LICENSE +190 -0
- package/NOTICE +23 -0
- package/core/cli.js +51 -0
- package/core/config.js +27 -0
- package/core/embedding/embedding-cache.js +467 -0
- package/core/embedding/embedding-local-model.js +845 -0
- package/core/embedding/embedding-remote.js +492 -0
- package/core/embedding/embedding-service.js +712 -0
- package/core/embedding/embedding-telemetry.js +219 -0
- package/core/embedding/index.js +40 -0
- package/core/graph/community-detector.js +294 -0
- package/core/graph/graph-expansion.js +839 -0
- package/core/graph/graph-extractor.js +2304 -0
- package/core/graph/graph-search.js +2148 -0
- package/core/graph/hcgs-generator.js +666 -0
- package/core/graph/index.js +16 -0
- package/core/graph/leiden-algorithm.js +547 -0
- package/core/graph/relationship-resolver.js +366 -0
- package/core/graph/repo-map.js +408 -0
- package/core/graph/summary-manager.js +549 -0
- package/core/indexing/artifact-builder.js +1054 -0
- package/core/indexing/ast-chunker.js +709 -0
- package/core/indexing/chunking/chunk-builder.js +170 -0
- package/core/indexing/chunking/markdown-chunker.js +503 -0
- package/core/indexing/chunking/plaintext-chunker.js +104 -0
- package/core/indexing/dedup/dedup-phase.js +159 -0
- package/core/indexing/dedup/exemplar-selector.js +65 -0
- package/core/indexing/document-chunker.js +56 -0
- package/core/indexing/incremental-parser.js +390 -0
- package/core/indexing/incremental-tracker.js +761 -0
- package/core/indexing/index-codebase-v21.js +472 -0
- package/core/indexing/index-maintainer.mjs +1674 -0
- package/core/indexing/index.js +90 -0
- package/core/indexing/indexer-ann.js +1077 -0
- package/core/indexing/indexer-build.js +742 -0
- package/core/indexing/indexer-phases.js +800 -0
- package/core/indexing/indexer-pool.js +764 -0
- package/core/indexing/indexer-sparse-gram.js +98 -0
- package/core/indexing/indexer-utils.js +536 -0
- package/core/indexing/indexer-worker.js +148 -0
- package/core/indexing/li-skip-policy.js +225 -0
- package/core/indexing/merkle-tracker.js +244 -0
- package/core/indexing/model-pool.js +166 -0
- package/core/infrastructure/code-graph-repository.js +120 -0
- package/core/infrastructure/codebase-repository.js +131 -0
- package/core/infrastructure/config/dedup.js +54 -0
- package/core/infrastructure/config/embedding.js +298 -0
- package/core/infrastructure/config/graph.js +80 -0
- package/core/infrastructure/config/index.js +82 -0
- package/core/infrastructure/config/indexing.js +8 -0
- package/core/infrastructure/config/platform.js +254 -0
- package/core/infrastructure/config/ranking.js +221 -0
- package/core/infrastructure/config/search.js +396 -0
- package/core/infrastructure/config/translation.js +89 -0
- package/core/infrastructure/config/vector-store.js +114 -0
- package/core/infrastructure/constants.js +86 -0
- package/core/infrastructure/coreml-cascade.js +909 -0
- package/core/infrastructure/coreml-cascade.json +46 -0
- package/core/infrastructure/coreml-provider.js +81 -0
- package/core/infrastructure/db-utils.js +69 -0
- package/core/infrastructure/dedup-hashing.js +83 -0
- package/core/infrastructure/hardware-capability.js +332 -0
- package/core/infrastructure/index.js +104 -0
- package/core/infrastructure/language-patterns/maps.js +121 -0
- package/core/infrastructure/language-patterns/registry-core.js +323 -0
- package/core/infrastructure/language-patterns/registry-data-query.js +155 -0
- package/core/infrastructure/language-patterns/registry-object-oriented.js +285 -0
- package/core/infrastructure/language-patterns/registry-tooling.js +240 -0
- package/core/infrastructure/language-patterns/registry-web-style.js +143 -0
- package/core/infrastructure/language-patterns/registry.js +19 -0
- package/core/infrastructure/language-patterns.js +141 -0
- package/core/infrastructure/llm-provider.js +733 -0
- package/core/infrastructure/manifest.json +46 -0
- package/core/infrastructure/maxsim.wasm +0 -0
- package/core/infrastructure/model-fetcher.js +423 -0
- package/core/infrastructure/model-registry.js +214 -0
- package/core/infrastructure/native-inference.js +587 -0
- package/core/infrastructure/native-resolver.js +187 -0
- package/core/infrastructure/native-sparse-gram.js +257 -0
- package/core/infrastructure/native-tokenizer.js +160 -0
- package/core/infrastructure/onnx-mutex.js +45 -0
- package/core/infrastructure/onnx-session-utils.js +261 -0
- package/core/infrastructure/ort-pipeline.js +111 -0
- package/core/infrastructure/project-detector.js +102 -0
- package/core/infrastructure/quantization.js +410 -0
- package/core/infrastructure/simd-distance.js +502 -0
- package/core/infrastructure/simd-distance.wasm +0 -0
- package/core/infrastructure/tree-sitter-provider.js +665 -0
- package/core/infrastructure/webgpu-maxsim.js +222 -0
- package/core/query/index.js +35 -0
- package/core/query/intent-detector.js +201 -0
- package/core/query/intent-router.js +156 -0
- package/core/query/query-router-catboost.js +222 -0
- package/core/query/query-router-ml.js +266 -0
- package/core/query/query-router.js +213 -0
- package/core/ranking/cascaded-scorer.js +379 -0
- package/core/ranking/flashrank.js +810 -0
- package/core/ranking/index.js +49 -0
- package/core/ranking/late-interaction-index.js +2383 -0
- package/core/ranking/late-interaction-model.js +812 -0
- package/core/ranking/local-reranker.js +374 -0
- package/core/ranking/mmr.js +379 -0
- package/core/ranking/quality-scorer.js +363 -0
- package/core/search/context-expander.js +1167 -0
- package/core/search/dedup/sibling-expander.js +327 -0
- package/core/search/index.js +16 -0
- package/core/search/search-boost.js +259 -0
- package/core/search/search-cli.js +544 -0
- package/core/search/search-format.js +282 -0
- package/core/search/search-fusion.js +327 -0
- package/core/search/search-hybrid.js +204 -0
- package/core/search/search-pattern-chunks.js +337 -0
- package/core/search/search-pattern-planner.js +439 -0
- package/core/search/search-pattern-prefilter.js +412 -0
- package/core/search/search-pattern-ripgrep.js +663 -0
- package/core/search/search-pattern.js +463 -0
- package/core/search/search-postprocess.js +452 -0
- package/core/search/search-semantic.js +706 -0
- package/core/search/search-server.js +554 -0
- package/core/search/session-daemon-prewarm.mjs +164 -0
- package/core/search/session-warmup.js +595 -0
- package/core/search/sweet-search.js +632 -0
- package/core/search/warmup-metrics.js +532 -0
- package/core/start-server.js +6 -0
- package/core/training/query-router/features/extractor.js +762 -0
- package/core/training/query-router/features/multilingual-patterns.js +431 -0
- package/core/training/query-router/features/text-segmenter.js +303 -0
- package/core/training/query-router/features/unicode-utils.js +383 -0
- package/core/training/query-router/output/v45_router_d4.js +11521 -0
- package/core/training/query-router/output/v46_router_d4.js +11498 -0
- package/core/vector-store/binary-heap.js +227 -0
- package/core/vector-store/binary-hnsw-index.js +1004 -0
- package/core/vector-store/float-vector-store.js +234 -0
- package/core/vector-store/hnsw-index.js +580 -0
- package/core/vector-store/index.js +39 -0
- package/core/vector-store/seismic-index.js +498 -0
- package/core/vocabulary/index.js +84 -0
- package/core/vocabulary/vocab-constants.js +20 -0
- package/core/vocabulary/vocab-miner-extractors.js +375 -0
- package/core/vocabulary/vocab-miner-nl.js +404 -0
- package/core/vocabulary/vocab-miner-utils.js +146 -0
- package/core/vocabulary/vocab-miner.js +574 -0
- package/core/vocabulary/vocab-prewarm-cli.js +110 -0
- package/core/vocabulary/vocab-ranker.js +492 -0
- package/core/vocabulary/vocab-warmer.js +523 -0
- package/core/vocabulary/vocab-warmup-orchestrator.js +425 -0
- package/core/vocabulary/vocabulary-utils.js +704 -0
- package/crates/wasm-router/pkg/package.json +13 -0
- package/crates/wasm-router/pkg/query_router_wasm.d.ts +36 -0
- package/crates/wasm-router/pkg/query_router_wasm.js +271 -0
- package/crates/wasm-router/pkg/query_router_wasm_bg.wasm +0 -0
- package/crates/wasm-router/pkg/query_router_wasm_bg.wasm.d.ts +19 -0
- package/mcp/config-gen.js +121 -0
- package/mcp/server.js +335 -0
- package/mcp/tool-handlers.js +476 -0
- package/package.json +131 -9
- package/scripts/benchmark-harness.js +794 -0
- package/scripts/init.js +1058 -0
- package/scripts/smoke-test.js +435 -0
- package/scripts/uninstall.js +478 -0
- package/scripts/verify-runtime.js +176 -0
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WASM-accelerated distance functions with JS fallback (Fix 6).
|
|
3
|
+
*
|
|
4
|
+
* Three-tier MaxSim acceleration:
|
|
5
|
+
* Tier 1: Native Rust + Rayon (60x) — parallel across CPU cores, NEON/AVX2 SIMD
|
|
6
|
+
* Tier 2: WASM SIMD f32x4 (16x) — portable, single-threaded
|
|
7
|
+
* Tier 3: JS fallback (3.5x) — norm-cached flat-buffer scoring
|
|
8
|
+
*
|
|
9
|
+
* Also provides WASM-accelerated:
|
|
10
|
+
* - hamming_distance, int8_dot, int8_norm_sq (simd-distance.wasm)
|
|
11
|
+
*
|
|
12
|
+
* Build: node scripts/build-wasm.js (WASM)
|
|
13
|
+
* cd crates/sweet-search-native && npx napi build --release --platform (native)
|
|
14
|
+
* cd crates/wasm-maxsim && RUSTFLAGS="-C target-feature=+simd128" cargo build --target wasm32-unknown-unknown --release (Rust WASM)
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { fileURLToPath } from 'url';
|
|
18
|
+
import { dirname, join } from 'path';
|
|
19
|
+
import { readFileSync, existsSync } from 'fs';
|
|
20
|
+
import { createRequire } from 'module';
|
|
21
|
+
import { resolveNativeAddon } from './native-resolver.js';
|
|
22
|
+
|
|
23
|
+
const DATA_OFFSET = 0; // SIMD popcount needs no LUT
|
|
24
|
+
|
|
25
|
+
let wasmExports = null;
|
|
26
|
+
let wasmMem = null;
|
|
27
|
+
let initDone = false;
|
|
28
|
+
|
|
29
|
+
// Rust-compiled MaxSim WASM (LLVM-optimized f32x4 SIMD + fused dequant)
|
|
30
|
+
let maxsimExports = null;
|
|
31
|
+
let maxsimMem = null;
|
|
32
|
+
|
|
33
|
+
// Native Rust + Rayon addon (Tier 1 — fastest)
|
|
34
|
+
let nativeMaxsim = null;
|
|
35
|
+
|
|
36
|
+
// =============================================================================
|
|
37
|
+
// INITIALIZATION
|
|
38
|
+
// =============================================================================
|
|
39
|
+
|
|
40
|
+
let initPromise = null;
|
|
41
|
+
|
|
42
|
+
async function initWasm() {
|
|
43
|
+
if (initDone) return true;
|
|
44
|
+
if (initPromise) return initPromise;
|
|
45
|
+
|
|
46
|
+
initPromise = (async () => {
|
|
47
|
+
try {
|
|
48
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
49
|
+
const require = createRequire(import.meta.url);
|
|
50
|
+
|
|
51
|
+
// Tier 1: Try native Rust addon (rayon parallel + NEON/AVX2 SIMD)
|
|
52
|
+
try {
|
|
53
|
+
const addonPath = resolveNativeAddon();
|
|
54
|
+
if (addonPath) {
|
|
55
|
+
nativeMaxsim = require(addonPath);
|
|
56
|
+
}
|
|
57
|
+
} catch {
|
|
58
|
+
// Native not available — fall through to WASM
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Tier 2a: Load hand-assembled SIMD distance WASM
|
|
62
|
+
const wasmPath = join(__dirname, 'simd-distance.wasm');
|
|
63
|
+
if (existsSync(wasmPath)) {
|
|
64
|
+
const wasmBuffer = readFileSync(wasmPath);
|
|
65
|
+
const { instance } = await WebAssembly.instantiate(wasmBuffer);
|
|
66
|
+
wasmExports = instance.exports;
|
|
67
|
+
wasmMem = new Uint8Array(wasmExports.memory.buffer);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Tier 2b: Load Rust-compiled MaxSim WASM (LLVM-optimized, f32x4 SIMD)
|
|
71
|
+
const maxsimPath = join(__dirname, 'maxsim.wasm');
|
|
72
|
+
if (existsSync(maxsimPath)) {
|
|
73
|
+
const maxsimBuffer = readFileSync(maxsimPath);
|
|
74
|
+
const { instance } = await WebAssembly.instantiate(maxsimBuffer);
|
|
75
|
+
maxsimExports = instance.exports;
|
|
76
|
+
maxsimMem = new Uint8Array(instance.exports.memory.buffer);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
initDone = true;
|
|
80
|
+
|
|
81
|
+
if (nativeMaxsim) {
|
|
82
|
+
console.log('[MaxSim] Tier 1: Native Rust + Rayon (parallel SIMD)');
|
|
83
|
+
} else if (maxsimExports || wasmExports?.maxsim_f32) {
|
|
84
|
+
console.log('[MaxSim] Tier 2: WASM SIMD f32x4');
|
|
85
|
+
} else {
|
|
86
|
+
console.log('[MaxSim] Tier 3: JS fallback');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return true;
|
|
90
|
+
} catch {
|
|
91
|
+
initPromise = null;
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
})();
|
|
95
|
+
|
|
96
|
+
return initPromise;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Eager non-blocking init
|
|
100
|
+
initWasm().catch(() => {});
|
|
101
|
+
|
|
102
|
+
// =============================================================================
|
|
103
|
+
// POPCOUNT LUT (JS fallback)
|
|
104
|
+
// =============================================================================
|
|
105
|
+
|
|
106
|
+
const POPCOUNT_LUT = new Uint8Array(256);
|
|
107
|
+
for (let i = 0; i < 256; i++) {
|
|
108
|
+
POPCOUNT_LUT[i] = (i & 1) + POPCOUNT_LUT[i >> 1];
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// =============================================================================
|
|
112
|
+
// HAMMING DISTANCE
|
|
113
|
+
// =============================================================================
|
|
114
|
+
|
|
115
|
+
export function wasmHammingDistance(a, b) {
|
|
116
|
+
if (wasmExports) {
|
|
117
|
+
const aPtr = DATA_OFFSET;
|
|
118
|
+
const bPtr = DATA_OFFSET + a.length;
|
|
119
|
+
// Re-acquire view in case memory grew
|
|
120
|
+
wasmMem = new Uint8Array(wasmExports.memory.buffer);
|
|
121
|
+
wasmMem.set(a, aPtr);
|
|
122
|
+
wasmMem.set(b, bPtr);
|
|
123
|
+
return wasmExports.hamming_distance(aPtr, bPtr, a.length);
|
|
124
|
+
}
|
|
125
|
+
// JS fallback
|
|
126
|
+
let distance = 0;
|
|
127
|
+
for (let i = 0; i < a.length; i++) {
|
|
128
|
+
distance += POPCOUNT_LUT[a[i] ^ b[i]];
|
|
129
|
+
}
|
|
130
|
+
return distance;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// =============================================================================
|
|
134
|
+
// INT8 COSINE SIMILARITY
|
|
135
|
+
// =============================================================================
|
|
136
|
+
|
|
137
|
+
export function wasmInt8Cosine(a, b) {
|
|
138
|
+
if (wasmExports) {
|
|
139
|
+
const aPtr = DATA_OFFSET;
|
|
140
|
+
const bPtr = DATA_OFFSET + a.length;
|
|
141
|
+
wasmMem = new Uint8Array(wasmExports.memory.buffer);
|
|
142
|
+
wasmMem.set(new Uint8Array(a.buffer, a.byteOffset, a.byteLength), aPtr);
|
|
143
|
+
wasmMem.set(new Uint8Array(b.buffer, b.byteOffset, b.byteLength), bPtr);
|
|
144
|
+
|
|
145
|
+
const dot = wasmExports.int8_dot(aPtr, bPtr, a.length);
|
|
146
|
+
const normA = wasmExports.int8_norm_sq(aPtr, a.length);
|
|
147
|
+
const normB = wasmExports.int8_norm_sq(bPtr, b.length);
|
|
148
|
+
|
|
149
|
+
const na = Math.sqrt(normA);
|
|
150
|
+
const nb = Math.sqrt(normB);
|
|
151
|
+
if (na === 0 || nb === 0) return 0;
|
|
152
|
+
return dot / (na * nb);
|
|
153
|
+
}
|
|
154
|
+
// JS fallback
|
|
155
|
+
let dot = 0, normA = 0, normB = 0;
|
|
156
|
+
for (let i = 0; i < a.length; i++) {
|
|
157
|
+
dot += a[i] * b[i];
|
|
158
|
+
normA += a[i] * a[i];
|
|
159
|
+
normB += b[i] * b[i];
|
|
160
|
+
}
|
|
161
|
+
const na = Math.sqrt(normA);
|
|
162
|
+
const nb = Math.sqrt(normB);
|
|
163
|
+
if (na === 0 || nb === 0) return 0;
|
|
164
|
+
return dot / (na * nb);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// =============================================================================
|
|
168
|
+
// INT8 BATCHED DOT PRODUCT (Phase 1: copy query once, score slab)
|
|
169
|
+
// =============================================================================
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Batch int8 dot product: score multiple candidate vectors against one query.
|
|
173
|
+
* Copies query once to WASM memory, candidates as contiguous slab.
|
|
174
|
+
* Returns raw int8 dot products (not normalized).
|
|
175
|
+
*
|
|
176
|
+
* For normalized int8 vectors (from L2-normalized floats), the raw dot
|
|
177
|
+
* approximates cosine * 127². Divide by 16129 for cosine-scale scores.
|
|
178
|
+
*
|
|
179
|
+
* @param {Int8Array} query - Query int8 vector
|
|
180
|
+
* @param {Int8Array[]} candidates - Array of candidate int8 vectors
|
|
181
|
+
* @returns {Int32Array} Raw dot products
|
|
182
|
+
*/
|
|
183
|
+
export function wasmInt8BatchDot(query, candidates) {
|
|
184
|
+
const dim = query.length;
|
|
185
|
+
const count = candidates.length;
|
|
186
|
+
if (count === 0) return new Int32Array(0);
|
|
187
|
+
|
|
188
|
+
if (wasmExports && wasmExports.int8_batch_dot) {
|
|
189
|
+
const queryPtr = DATA_OFFSET;
|
|
190
|
+
const slabPtr = DATA_OFFSET + dim;
|
|
191
|
+
// Scores buffer: 4 bytes per candidate, placed after the slab
|
|
192
|
+
const scoresPtr = slabPtr + count * dim;
|
|
193
|
+
// Align scores pointer to 4-byte boundary
|
|
194
|
+
const alignedScoresPtr = (scoresPtr + 3) & ~3;
|
|
195
|
+
const needed = alignedScoresPtr + count * 4;
|
|
196
|
+
|
|
197
|
+
wasmMem = new Uint8Array(wasmExports.memory.buffer);
|
|
198
|
+
if (needed > wasmMem.length) {
|
|
199
|
+
return _jsInt8BatchDot(query, candidates);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Copy query once
|
|
203
|
+
wasmMem.set(new Uint8Array(query.buffer, query.byteOffset, query.byteLength), queryPtr);
|
|
204
|
+
|
|
205
|
+
// Copy all candidates as contiguous slab
|
|
206
|
+
for (let i = 0; i < count; i++) {
|
|
207
|
+
const v = candidates[i];
|
|
208
|
+
wasmMem.set(new Uint8Array(v.buffer, v.byteOffset, v.byteLength), slabPtr + i * dim);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Single WASM call: scores all candidates internally, writes to output buffer
|
|
212
|
+
wasmExports.int8_batch_dot(queryPtr, slabPtr, count, dim, alignedScoresPtr);
|
|
213
|
+
|
|
214
|
+
// Read scores from WASM memory
|
|
215
|
+
return new Int32Array(wasmExports.memory.buffer, alignedScoresPtr, count);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return _jsInt8BatchDot(query, candidates);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function _jsInt8BatchDot(query, candidates) {
|
|
222
|
+
const dim = query.length;
|
|
223
|
+
const scores = new Int32Array(candidates.length);
|
|
224
|
+
for (let c = 0; c < candidates.length; c++) {
|
|
225
|
+
let dot = 0;
|
|
226
|
+
const v = candidates[c];
|
|
227
|
+
for (let i = 0; i < dim; i++) dot += query[i] * v[i];
|
|
228
|
+
scores[c] = dot;
|
|
229
|
+
}
|
|
230
|
+
return scores;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// =============================================================================
|
|
234
|
+
// FLOAT32 BATCHED DOT PRODUCT (Phase 2: Stage 2.5 float rescore)
|
|
235
|
+
// =============================================================================
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Batch float32 dot product: score multiple float vectors against one query.
|
|
239
|
+
* Pure JS implementation — V8 JIT is competitive with WASM for float math.
|
|
240
|
+
*
|
|
241
|
+
* @param {Float32Array} query - Query float vector
|
|
242
|
+
* @param {Float32Array[]} candidates - Array of candidate float vectors
|
|
243
|
+
* @returns {Float64Array} Dot product scores
|
|
244
|
+
*/
|
|
245
|
+
export function float32BatchDot(query, candidates) {
|
|
246
|
+
const dim = query.length;
|
|
247
|
+
const scores = new Float64Array(candidates.length);
|
|
248
|
+
for (let c = 0; c < candidates.length; c++) {
|
|
249
|
+
const v = candidates[c];
|
|
250
|
+
if (v.length !== dim) {
|
|
251
|
+
throw new Error(`float32BatchDot dimension mismatch: query=${dim}, candidate[${c}]=${v.length}`);
|
|
252
|
+
}
|
|
253
|
+
let dot = 0;
|
|
254
|
+
for (let i = 0; i < dim; i++) dot += query[i] * v[i];
|
|
255
|
+
scores[c] = dot;
|
|
256
|
+
}
|
|
257
|
+
return scores;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// For L2-normalized vectors, dot product = cosine similarity.
|
|
261
|
+
// Use float32BatchDot directly — no separate cosine wrapper needed.
|
|
262
|
+
|
|
263
|
+
// =============================================================================
|
|
264
|
+
// INT8 DOT PRODUCT (raw, for asymmetric distance)
|
|
265
|
+
// =============================================================================
|
|
266
|
+
|
|
267
|
+
export function wasmInt8Dot(a, b) {
|
|
268
|
+
if (wasmExports) {
|
|
269
|
+
const aPtr = DATA_OFFSET;
|
|
270
|
+
const bPtr = DATA_OFFSET + a.length;
|
|
271
|
+
wasmMem = new Uint8Array(wasmExports.memory.buffer);
|
|
272
|
+
wasmMem.set(new Uint8Array(a.buffer, a.byteOffset, a.byteLength), aPtr);
|
|
273
|
+
wasmMem.set(new Uint8Array(b.buffer, b.byteOffset, b.byteLength), bPtr);
|
|
274
|
+
return wasmExports.int8_dot(aPtr, bPtr, a.length);
|
|
275
|
+
}
|
|
276
|
+
// JS fallback
|
|
277
|
+
let dot = 0;
|
|
278
|
+
for (let i = 0; i < a.length; i++) dot += a[i] * b[i];
|
|
279
|
+
return dot;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// =============================================================================
|
|
283
|
+
// STATUS
|
|
284
|
+
// =============================================================================
|
|
285
|
+
|
|
286
|
+
// =============================================================================
|
|
287
|
+
// ASYMMETRIC DISTANCE (1-bit doc × int4 query)
|
|
288
|
+
// =============================================================================
|
|
289
|
+
|
|
290
|
+
export function wasmAsymmetricDistance(docBinary, queryInt4, queryNormScaled) {
|
|
291
|
+
if (wasmExports) {
|
|
292
|
+
const docPtr = DATA_OFFSET;
|
|
293
|
+
const queryPtr = DATA_OFFSET + docBinary.length;
|
|
294
|
+
wasmMem = new Uint8Array(wasmExports.memory.buffer);
|
|
295
|
+
wasmMem.set(docBinary, docPtr);
|
|
296
|
+
wasmMem.set(new Uint8Array(queryInt4.buffer, queryInt4.byteOffset, queryInt4.byteLength), queryPtr);
|
|
297
|
+
return wasmExports.asymmetric_distance(docPtr, queryPtr, queryInt4.length, queryNormScaled);
|
|
298
|
+
}
|
|
299
|
+
// JS fallback
|
|
300
|
+
let approxDot = 0;
|
|
301
|
+
const dim = queryInt4.length;
|
|
302
|
+
for (let byteIdx = 0; byteIdx < docBinary.length; byteIdx++) {
|
|
303
|
+
let byte = docBinary[byteIdx];
|
|
304
|
+
const baseIdx = byteIdx * 8;
|
|
305
|
+
for (let bit = 7; bit >= 0; bit--) {
|
|
306
|
+
const idx = baseIdx + (7 - bit);
|
|
307
|
+
if (idx >= dim) break;
|
|
308
|
+
if (byte & (1 << bit)) approxDot += queryInt4[idx];
|
|
309
|
+
else approxDot -= queryInt4[idx];
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
return queryNormScaled - 2 * approxDot;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// =============================================================================
|
|
316
|
+
// WASM MAXSIM F32
|
|
317
|
+
// =============================================================================
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Compute MaxSim score entirely in WASM f32 arithmetic.
|
|
321
|
+
*
|
|
322
|
+
* Copies query and doc float32 tokens to WASM memory, calls the WASM
|
|
323
|
+
* maxsim_f32 kernel, returns the score. ~10-30x faster than JS for
|
|
324
|
+
* large Q×D×dim workloads.
|
|
325
|
+
*
|
|
326
|
+
* Falls back to null if WASM is unavailable or data exceeds memory.
|
|
327
|
+
*
|
|
328
|
+
* @param {Float32Array} queryFlat - Q × dim float32 (contiguous)
|
|
329
|
+
* @param {Float32Array} docFlat - D × dim float32 (contiguous)
|
|
330
|
+
* @param {number} numQ - Number of query tokens
|
|
331
|
+
* @param {number} numD - Number of document tokens
|
|
332
|
+
* @param {number} dim - Token dimension
|
|
333
|
+
* @returns {number|null} MaxSim score, or null if WASM unavailable
|
|
334
|
+
*/
|
|
335
|
+
export function wasmMaxSimF32(queryFlat, docFlat, numQ, numD, dim) {
|
|
336
|
+
// Use Rust-compiled WASM only (handles arbitrary dim via scalar tail)
|
|
337
|
+
if (!maxsimExports?.maxsim_f32) return null;
|
|
338
|
+
const exports = maxsimExports;
|
|
339
|
+
|
|
340
|
+
const mem = maxsimExports
|
|
341
|
+
? new Uint8Array(maxsimExports.memory.buffer)
|
|
342
|
+
: (wasmMem = new Uint8Array(wasmExports.memory.buffer));
|
|
343
|
+
|
|
344
|
+
const qBytes = numQ * dim * 4;
|
|
345
|
+
const dBytes = numD * dim * 4;
|
|
346
|
+
|
|
347
|
+
if (qBytes + dBytes + 1024 > mem.length) return null;
|
|
348
|
+
|
|
349
|
+
const qPtr = DATA_OFFSET;
|
|
350
|
+
const dPtr = DATA_OFFSET + qBytes;
|
|
351
|
+
|
|
352
|
+
mem.set(new Uint8Array(queryFlat.buffer, queryFlat.byteOffset, qBytes), qPtr);
|
|
353
|
+
mem.set(new Uint8Array(docFlat.buffer, docFlat.byteOffset, dBytes), dPtr);
|
|
354
|
+
|
|
355
|
+
return exports.maxsim_f32(qPtr, dPtr, numQ, numD, dim);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Compute MaxSim with fused int8 dequantization in WASM.
|
|
360
|
+
*
|
|
361
|
+
* Skips JS-side dequantization entirely: copies raw int8 tokens (4x smaller)
|
|
362
|
+
* to WASM memory and dequantizes inline during scoring.
|
|
363
|
+
*
|
|
364
|
+
* @param {Float32Array} queryFlat - Q × dim float32 (contiguous)
|
|
365
|
+
* @param {Int8Array} docInt8 - Raw int8 document tokens (D × dim)
|
|
366
|
+
* @param {number} numQ - Number of query tokens
|
|
367
|
+
* @param {number} numD - Number of document tokens
|
|
368
|
+
* @param {number} dim - Token dimension
|
|
369
|
+
* @param {number} min - Quantization min
|
|
370
|
+
* @param {number} scale - Quantization scale
|
|
371
|
+
* @returns {number|null} MaxSim score, or null if unavailable
|
|
372
|
+
*/
|
|
373
|
+
export function wasmMaxSimDequant(queryFlat, docInt8, numQ, numD, dim, min, scale) {
|
|
374
|
+
if (!maxsimExports?.maxsim_dequant) return null;
|
|
375
|
+
|
|
376
|
+
const qBytes = numQ * dim * 4; // float32
|
|
377
|
+
const dBytes = numD * dim; // int8 (4x smaller!)
|
|
378
|
+
|
|
379
|
+
const mem = new Uint8Array(maxsimExports.memory.buffer);
|
|
380
|
+
if (qBytes + dBytes + 1024 > mem.length) return null;
|
|
381
|
+
|
|
382
|
+
const qPtr = DATA_OFFSET;
|
|
383
|
+
const dPtr = DATA_OFFSET + qBytes;
|
|
384
|
+
|
|
385
|
+
// Copy query (float32)
|
|
386
|
+
mem.set(new Uint8Array(queryFlat.buffer, queryFlat.byteOffset, qBytes), qPtr);
|
|
387
|
+
// Copy doc int8 (4x smaller copy than float32!)
|
|
388
|
+
mem.set(new Uint8Array(docInt8.buffer, docInt8.byteOffset, dBytes), dPtr);
|
|
389
|
+
|
|
390
|
+
return maxsimExports.maxsim_dequant(qPtr, dPtr, numQ, numD, dim, min, scale);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// Shared layout: copy query + doc + per-token params into WASM memory, return pointers.
|
|
394
|
+
// Returns null if data doesn't fit in WASM memory.
|
|
395
|
+
function _wasmPerTokenLayout(queryFlat, docData, minArray, scaleArray, tokenNorms, numQ, numD, dim, dBytes) {
|
|
396
|
+
const qBytes = numQ * dim * 4;
|
|
397
|
+
const paramBytes = numD * 4 * 3;
|
|
398
|
+
if (!maxsimMem || maxsimMem.buffer !== maxsimExports.memory.buffer) {
|
|
399
|
+
maxsimMem = new Uint8Array(maxsimExports.memory.buffer);
|
|
400
|
+
}
|
|
401
|
+
if (qBytes + dBytes + paramBytes + 1024 > maxsimMem.length) return null;
|
|
402
|
+
|
|
403
|
+
const qPtr = DATA_OFFSET;
|
|
404
|
+
const dPtr = qPtr + qBytes;
|
|
405
|
+
const minPtr = dPtr + dBytes;
|
|
406
|
+
const scalePtr = minPtr + numD * 4;
|
|
407
|
+
const normPtr = scalePtr + numD * 4;
|
|
408
|
+
|
|
409
|
+
maxsimMem.set(new Uint8Array(queryFlat.buffer, queryFlat.byteOffset, qBytes), qPtr);
|
|
410
|
+
maxsimMem.set(new Uint8Array(docData.buffer, docData.byteOffset, dBytes), dPtr);
|
|
411
|
+
maxsimMem.set(new Uint8Array(minArray.buffer, minArray.byteOffset, numD * 4), minPtr);
|
|
412
|
+
maxsimMem.set(new Uint8Array(scaleArray.buffer, scaleArray.byteOffset, numD * 4), scalePtr);
|
|
413
|
+
maxsimMem.set(new Uint8Array(tokenNorms.buffer, tokenNorms.byteOffset, numD * 4), normPtr);
|
|
414
|
+
|
|
415
|
+
return { qPtr, dPtr, minPtr, scalePtr, normPtr };
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
export function wasmMaxSimDequantPerToken(queryFlat, docInt8, minArray, scaleArray, tokenNorms, numQ, numD, dim) {
|
|
419
|
+
if (!maxsimExports?.maxsim_dequant_pertoken) return null;
|
|
420
|
+
const ptrs = _wasmPerTokenLayout(queryFlat, docInt8, minArray, scaleArray, tokenNorms, numQ, numD, dim, numD * dim);
|
|
421
|
+
if (!ptrs) return null;
|
|
422
|
+
return maxsimExports.maxsim_dequant_pertoken(ptrs.qPtr, ptrs.dPtr, ptrs.minPtr, ptrs.scalePtr, ptrs.normPtr, numQ, numD, dim);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
export function wasmMaxSimDequant4Bit(queryFlat, docPacked, minArray, scaleArray, tokenNorms, numQ, numD, dim) {
|
|
426
|
+
if (!maxsimExports?.maxsim_dequant_4bit) return null;
|
|
427
|
+
const ptrs = _wasmPerTokenLayout(queryFlat, docPacked, minArray, scaleArray, tokenNorms, numQ, numD, dim, numD * Math.ceil(dim / 2));
|
|
428
|
+
if (!ptrs) return null;
|
|
429
|
+
return maxsimExports.maxsim_dequant_4bit(ptrs.qPtr, ptrs.dPtr, ptrs.minPtr, ptrs.scalePtr, ptrs.normPtr, numQ, numD, dim);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// =============================================================================
|
|
433
|
+
// NATIVE MAXSIM BATCH (Tier 1 — rayon parallel)
|
|
434
|
+
// =============================================================================
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Score all candidates in parallel using the native Rust + Rayon addon.
|
|
438
|
+
*
|
|
439
|
+
* @param {Float32Array} queryFlat - Q × dim float32
|
|
440
|
+
* @param {number} numQ
|
|
441
|
+
* @param {number} dim
|
|
442
|
+
* @param {Array<{tokens: Int8Array|Buffer, numTokens: number, dim: number, min: number, scale: number}>} candidates
|
|
443
|
+
* @returns {number[]|null} Array of MaxSim scores, or null if native unavailable
|
|
444
|
+
*/
|
|
445
|
+
export function nativeMaxSimBatch(queryFlat, numQ, dim, candidates) {
|
|
446
|
+
if (!nativeMaxsim) return null;
|
|
447
|
+
return nativeMaxsim.maxsimScoreBatch(queryFlat, numQ, dim, candidates);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Native batch scoring with per-token min/scale and pre-stored norms (Phase 4).
|
|
452
|
+
* @param {Float32Array} queryFlat
|
|
453
|
+
* @param {number} numQ
|
|
454
|
+
* @param {number} dim
|
|
455
|
+
* @param {Array<{tokens: Buffer, numTokens: number, dim: number, minArray: Float32Array, scaleArray: Float32Array, tokenNorms: Float32Array}>} candidates
|
|
456
|
+
* @returns {number[]|null}
|
|
457
|
+
*/
|
|
458
|
+
export function nativeMaxSimBatchPerToken(queryFlat, numQ, dim, candidates) {
|
|
459
|
+
if (!nativeMaxsim?.maxsimScoreBatchPertoken) return null;
|
|
460
|
+
return nativeMaxsim.maxsimScoreBatchPertoken(queryFlat, numQ, dim, candidates);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Native batch scoring with 4-bit nibble-packed tokens (Phase 4).
|
|
465
|
+
* @param {Float32Array} queryFlat
|
|
466
|
+
* @param {number} numQ
|
|
467
|
+
* @param {number} dim
|
|
468
|
+
* @param {Array<{tokens: Buffer, numTokens: number, dim: number, minArray: Float32Array, scaleArray: Float32Array, tokenNorms: Float32Array}>} candidates
|
|
469
|
+
* @returns {number[]|null}
|
|
470
|
+
*/
|
|
471
|
+
export function nativeMaxSimBatch4Bit(queryFlat, numQ, dim, candidates) {
|
|
472
|
+
if (!nativeMaxsim?.maxsimScoreBatch4Bit) return null;
|
|
473
|
+
return nativeMaxsim.maxsimScoreBatch4Bit(queryFlat, numQ, dim, candidates);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
export function isNativePerTokenAvailable() {
|
|
477
|
+
return !!nativeMaxsim?.maxsimScoreBatchPertoken;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
export function isNative4BitAvailable() {
|
|
481
|
+
return !!nativeMaxsim?.maxsimScoreBatch4Bit;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
export function isWasmAvailable() {
|
|
485
|
+
return !!wasmExports;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
export function isMaxSimWasmAvailable() {
|
|
489
|
+
return !!maxsimExports;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
export function isNativeMaxSimAvailable() {
|
|
493
|
+
return !!nativeMaxsim;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
export function getMaxSimTier() {
|
|
497
|
+
if (nativeMaxsim) return 'native';
|
|
498
|
+
if (maxsimExports || wasmExports?.maxsim_f32) return 'wasm';
|
|
499
|
+
return 'js';
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
export { initWasm };
|
|
Binary file
|