Skip to content

Commit 7ddbebf

Browse files
committed
zk version too slow
1 parent f374d8f commit 7ddbebf

5 files changed

Lines changed: 79 additions & 10 deletions

File tree

AGENTS.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,3 @@ This repository implements zero-knowledge IVF–PQ primitives as a Rust `cdylib`
3434
- Reference related issues in commit messages or PR descriptions when applicable.
3535
- Pull requests should describe the motivation, summarize key changes, and note how you tested (commands and datasets). Include benchmark details for performance-sensitive changes.
3636

37-
BUGFIX: 用的k-means太慢了, 同时标准方案用的也是量化版本的k-means, 这是有问题的.

bench/acc_bench.py

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import json
22
import math
3+
import time
34
from pathlib import Path
45
from typing import Dict, List, Tuple
6+
from tqdm import tqdm
57

68
import numpy as np
79

@@ -58,9 +60,20 @@ def _load_cached(path: Path) -> List[Dict[str, float]]:
5860
"standard_pass_at_k": float(run["standard_pass_at_k"]),
5961
"zk_pass_at_k": float(run["zk_pass_at_k"]),
6062
}
61-
# 可选保存的 zk_n(每簇 padding 长度),若存在则保留
62-
if "zk_n" in run:
63-
cleaned["zk_n"] = float(run["zk_n"])
63+
# 可选保存的附加信息(每簇 padding 长度、各阶段时间),若存在则保留
64+
optional_keys = [
65+
"zk_n",
66+
"bruteforce_time",
67+
"standard_train_time",
68+
"standard_query_time",
69+
"zk_train_time",
70+
"zk_query_time",
71+
"standard_recall_at_k",
72+
"zk_recall_at_k",
73+
]
74+
for key in optional_keys:
75+
if key in run:
76+
cleaned[key] = float(run[key])
6477
out.append(cleaned)
6578
return out
6679

@@ -139,14 +152,18 @@ def _run_once(
139152
raise ValueError("top_k must be in [1, N]")
140153

141154
# 1. 预先计算 brute-force L2 KNN 作为 ground truth
155+
t0 = time.time()
142156
gt_topk = [brute_force_knn(base, queries[i], top_k) for i in range(Q)]
157+
bruteforce_time = time.time() - t0
158+
print(f"[acc_bench] bruteforce_time={bruteforce_time:.3f}s")
143159

144160
# 为本次 run 生成不同的随机种子,使多次 run 之间有随机性
145161
rng = np.random.default_rng()
146162
std_seed = int(rng.integers(0, 2**31 - 1))
147163
zk_seed = int(rng.integers(0, 2**31 - 1))
148164

149165
# 2. 非 ZK 版本:使用浮点 standard IVF-PQ
166+
t0 = time.time()
150167
std_labels, std_center, std_code_books, std_quant_vecs, std_id_groups = (
151168
ivf_pq_learn(
152169
base,
@@ -156,9 +173,13 @@ def _run_once(
156173
random_state=std_seed,
157174
)
158175
)
176+
standard_train_time = time.time() - t0
177+
print(f"[acc_bench] standard_train_time={standard_train_time:.3f}s")
159178

160179
std_pass_list: List[float] = []
161-
for i in range(Q):
180+
std_recall_list: List[float] = []
181+
t0 = time.time()
182+
for i in tqdm(range(Q), "非zk版本"):
162183
pred = ivf_pq_query(
163184
queries[i],
164185
top_k,
@@ -171,8 +192,13 @@ def _run_once(
171192
)
172193
inter = np.intersect1d(pred, gt_topk[i])
173194
std_pass_list.append(float(inter.size) / float(top_k))
195+
best_gt = int(gt_topk[i][0])
196+
std_recall_list.append(1.0 if best_gt in pred else 0.0)
197+
standard_query_time = time.time() - t0
198+
print(f"[acc_bench] standard_query_time={standard_query_time:.3f}s")
174199

175200
# 3. ZK 版本:首先 rescale,然后使用 zk 版本的 learn + query
201+
t0 = time.time()
176202
scaled_base, v_min, v_max = rescale_database(base, scale_n)
177203
if cluster_bound is not None:
178204
(
@@ -207,9 +233,13 @@ def _run_once(
207233

208234
# 计算 ZK 证明中每簇需要 padding 到的容量 n(power-of-two 容量)
209235
zk_n = _build_cluster_capacity(zk_id_groups, n_probe)
236+
zk_train_time = time.time() - t0
237+
print(f"[acc_bench] zk_train_time={zk_train_time:.3f}s")
210238

211239
zk_pass_list: List[float] = []
212-
for i in range(Q):
240+
zk_recall_list: List[float] = []
241+
t0 = time.time()
242+
for i in tqdm(range(Q), "zk版本"):
213243
scaled_query = rescale_query(queries[i], scale_n, v_min, v_max)
214244
pred_zk, _ = zk_ivf_pq_query(
215245
scaled_query,
@@ -223,11 +253,22 @@ def _run_once(
223253
)
224254
inter = np.intersect1d(pred_zk, gt_topk[i])
225255
zk_pass_list.append(float(inter.size) / float(top_k))
256+
best_gt = int(gt_topk[i][0])
257+
zk_recall_list.append(1.0 if best_gt in pred_zk else 0.0)
258+
zk_query_time = time.time() - t0
259+
print(f"[acc_bench] zk_query_time={zk_query_time:.3f}s")
226260

227261
result = {
228262
"standard_pass_at_k": float(np.mean(std_pass_list)),
229263
"zk_pass_at_k": float(np.mean(zk_pass_list)),
264+
"standard_recall_at_k": float(np.mean(std_recall_list)),
265+
"zk_recall_at_k": float(np.mean(zk_recall_list)),
230266
"zk_n": float(zk_n),
267+
"bruteforce_time": float(bruteforce_time),
268+
"standard_train_time": float(standard_train_time),
269+
"standard_query_time": float(standard_query_time),
270+
"zk_train_time": float(zk_train_time),
271+
"zk_query_time": float(zk_query_time),
231272
}
232273
return result
233274

ivf_pq/__init__.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,32 @@ def brute_force_knn(
1313
base_vecs = np.asarray(base_vecs)
1414
query_vec = np.asarray(query_vec, dtype=base_vecs.dtype)
1515

16+
if base_vecs.ndim != 2:
17+
raise ValueError("base_vecs must be a 2D array of shape (N, D)")
18+
if query_vec.ndim != 1:
19+
raise ValueError("query_vec must be a 1D array of shape (D,)")
20+
21+
N, D = base_vecs.shape
22+
if query_vec.shape[0] != D:
23+
raise ValueError(f"dimension mismatch: base_vecs D={D}, query_vec D={query_vec.shape[0]}")
24+
25+
if top_k <= 0:
26+
raise ValueError("top_k must be positive")
27+
if top_k > N:
28+
top_k = N
29+
1630
diff = base_vecs - query_vec
1731
dist2 = np.sum(diff * diff, axis=1)
18-
topk_idx = np.argsort(dist2)[:top_k]
32+
33+
# 使用 argpartition 先选出前 top_k 小的元素,再在这 top_k 内做完整排序,
34+
# 将复杂度从 O(N log N) 降为 O(N) + O(top_k log top_k),在 N 很大且 top_k 很小时明显加速。
35+
if top_k == N:
36+
topk_idx = np.argsort(dist2)
37+
else:
38+
partial = np.argpartition(dist2, top_k - 1)[:top_k]
39+
order = np.argsort(dist2[partial])
40+
topk_idx = partial[order]
41+
1942
return topk_idx.astype(np.int64)
2043

2144

ivf_pq/zk.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ def ivf_pq_learn(
2929
center, id_groups, labels = kmeans_with_ids(
3030
vecs, n_list, niter=n_iter, random_state=random_state
3131
)
32+
center = np.rint(center).astype(np.int64) # 转64
3233

3334
changed_count = 0
3435
if cluster_bound is not None:
@@ -40,6 +41,7 @@ def ivf_pq_learn(
4041
)
4142

4243
centers = center[labels]
44+
print("IVF训练完成")
4345

4446
res_vecs = vecs - centers
4547

tests/acc_bench.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,22 @@
1+
import time
12
from vec_data_load.sift import SIFT
23
from bench.acc_bench import run_accuracy_bench
34

5+
stime = time.time()
46
sift = SIFT("data/gist/")
57
print(sift.base_vecs.shape)
8+
print(sift.query_vecs.shape)
69
summary = run_accuracy_bench(
710
sift.base_vecs,
811
sift.query_vecs,
9-
top_k=10,
12+
top_k=100,
1013
name="gist_1m",
1114
n_list=1024,
12-
M=30,
15+
M=8,
1316
K=256,
1417
n_probe=8,
15-
num_runs=5,
18+
num_runs=1,
1619
cluster_bound=2048,
1720
)
1821
print(summary)
22+
print((time.time() - stime) / 3600)

0 commit comments

Comments
 (0)