Use NumPy for the dense baseline and torch for GPU. Normalize once — cosine similarity then reduces to a dot product.
import numpy as np
def cosine_search(q, M, k=10):
q = q / (np.linalg.norm(q) + 1e-12)
M = M / (np.linalg.norm(M, axis=1, keepdims=True) + 1e-12)
sims = M @ q
idx = np.argpartition(-sims, k)[:k] # O(n), not O(n log n)
return idx[np.argsort(-sims[idx])]
Gotchas: forgetting eps in the denominator; using 32-bit float for 1024-d embeddings; recomputing norms inside the inner loop. Strong candidates add a "scale to 1M vectors" hook — block-wise matmul + IVF-PQ or FAISS — and call out the latency ceiling (~10 ms for 1M vectors on CPU). Cosine vs dot product vs Euclidean: un-normalized embeddings encode magnitude (popularity/confidence), so the choice matters.