code wiki / _hdl_build / nx_vec_nsw.nx
nx_vec_nsw.nx source
↩ module page · 88 lines · 4272 B
1// nx_vec_nsw.nx -- R-VEC-3 of the onsite-search S-class ladder: SOVEREIGN approximate-nearest-neighbor INDEX via
2// a Navigable Small World proximity graph (LIBRARY). "Illustration of the multi-layered search process of a
3// Hierarchical Navigable Small World graph" (cited srch_hnsw.raw) -- this builds the SINGLE-LAYER NSW core (the
4// base HNSW stacks into layers); an ANN search "is allowed to return points whose distance is at most c*d"
5// (cited srch_ann.raw) for huge speedups over brute force. Reuses R-VEC-0 vr_cos_milli for distance (higher
6// cosine = nearer). HONEST SCOPE: single-layer NSW + ef-beam greedy search; hierarchical layers = the named
7// HNSW extension. Sub-linear because search only walks the graph neighborhood, not all N vectors.
8//
9// exports: vr_nsw_build, vr_nsw_search. license_tier: ORIGINAL
10import "nx_syscalls.nx"
11import "nx_vec_kernel.nx"
12const K_MAGIC_2000000: i64 = 2000000
13
14// add directed edge a->b (dedup, capacity MMAX)
15func nsw_addedge(adj: *i64, deg: *i64, MMAX: i64, a: i64, b: i64) -> i64 {
16 if deg[a] >= MMAX { return 0 }
17 var e: i64 = 0
18 while e < deg[a] { if adj[a*MMAX+e] == b { return 0 } e = e + 1 }
19 adj[a*MMAX+deg[a]] = b; deg[a] = deg[a] + 1; return 1
20}
21
22// build the NSW graph: insert nodes in order; each links (bidirectionally) to its M nearest already-inserted.
23func vr_nsw_build(V: *i64, N: i64, D: i64, M: i64, MMAX: i64, adj: *i64, deg: *i64) -> i64 {
24 var i: i64 = 0; while i < N { deg[i] = 0; i = i + 1 }
25 let cosb: *i64 = sys_mmap(8*(N+2)) as *i64
26 let used: *i64 = sys_mmap(8*(N+2)) as *i64
27 i = 1
28 while i < N {
29 let vi: *i64 = ((V as i64) + i*D*8) as *i64
30 var j: i64 = 0
31 while j < i { let vj: *i64 = ((V as i64) + j*D*8) as *i64; cosb[j] = vr_cos_milli(vi, vj, D); used[j] = 0; j = j + 1 }
32 var lim: i64 = M; if i < M { lim = i }
33 var sel: i64 = 0
34 while sel < lim {
35 var bestj: i64 = 0-1; var bc: i64 = 0-K_MAGIC_2000000
36 var t: i64 = 0
37 while t < i { if used[t] == 0 { if cosb[t] > bc { bc = cosb[t]; bestj = t } } t = t + 1 }
38 if bestj < 0 { sel = lim } else {
39 used[bestj] = 1
40 nsw_addedge(adj, deg, MMAX, i, bestj)
41 nsw_addedge(adj, deg, MMAX, bestj, i)
42 sel = sel + 1
43 }
44 }
45 i = i + 1
46 }
47 return 0
48}
49
50// ef-beam greedy search from `entry`; returns the node id of the best (nearest) found. Buffers caller-provided.
51func vr_nsw_search(V: *i64, N: i64, D: i64, query: *i64, entry: i64, ef: i64, MMAX: i64, adj: *i64, deg: *i64,
52 visited: *i64, res_id: *i64, res_cos: *i64, res_exp: *i64) -> i64 {
53 var i: i64 = 0; while i < N { visited[i] = 0; i = i + 1 }
54 visited[entry] = 1
55 let ve: *i64 = ((V as i64) + entry*D*8) as *i64
56 res_id[0] = entry; res_cos[0] = vr_cos_milli(query, ve, D); res_exp[0] = 0
57 var rn: i64 = 1
58 var guard: i64 = 0
59 while guard < N + 8 {
60 guard = guard + 1
61 var best: i64 = 0-1; var bc: i64 = 0-K_MAGIC_2000000
62 var t: i64 = 0
63 while t < rn { if res_exp[t] == 0 { if res_cos[t] > bc { bc = res_cos[t]; best = t } } t = t + 1 }
64 if best < 0 { guard = N + 8 } else {
65 res_exp[best] = 1
66 let c: i64 = res_id[best]
67 var e: i64 = 0
68 while e < deg[c] {
69 let nb: i64 = adj[c*MMAX+e]
70 if visited[nb] == 0 {
71 visited[nb] = 1
72 let vn: *i64 = ((V as i64) + nb*D*8) as *i64
73 let d: i64 = vr_cos_milli(query, vn, D)
74 if rn < ef { res_id[rn] = nb; res_cos[rn] = d; res_exp[rn] = 0; rn = rn + 1 }
75 else {
76 var mi: i64 = 0; var mc: i64 = res_cos[0]; var u: i64 = 1
77 while u < rn { if res_cos[u] < mc { mc = res_cos[u]; mi = u } u = u + 1 }
78 if d > mc { res_id[mi] = nb; res_cos[mi] = d; res_exp[mi] = 0 }
79 }
80 }
81 e = e + 1
82 }
83 }
84 }
85 var bi: i64 = 0; var bcos: i64 = res_cos[0]; var w: i64 = 1
86 while w < rn { if res_cos[w] > bcos { bcos = res_cos[w]; bi = w } w = w + 1 }
87 return res_id[bi]
88}