返回

文章详情

浏览器中的语义/混合搜索

Hacker News2026年7月11日 13:04

听这篇文章您的浏览器不支持音频元素 八年前,我使用 Lunr.js 为这个博客添加了客户端搜索。它在构建时创建一个倒排索引,作为 JSON 发送,并在您的浏览器中匹配字符串。无需服务器端引擎。从那时起它一直运作良好,这意味着如果您输入一个实际在文章中的单词,它会找到那篇文章。今年早些时候,我用大约 250 行 Python 编写了一个语义搜索引擎(那种让您在搜索 “英国的酒精饮料灾难” 时能找到 “伦敦啤酒洪水” 的类型,因为它理解啤酒是酒精饮料,而洪水是灾难)。那个需要一台安装了句子变换器且有几百兆 PyTorch 的机器;在生产服务器上提供类似的服务需要强大的机器和昂贵的内存。这不是您可以在浏览器标签中运行的东西。因此,这个博客有关键字搜索,可以在任何地方运行但不理解任何东西,却没有语义搜索,理解事物但无法在静态站点附近运行。这篇文章是关于弥补这一差距的:一种完全在您的浏览器中运行的语义搜索,没有服务器和 API,整个模型基本上是一个4 MB的查找表。您可以尝试我基准测试的三个模型,在下面的部分中,在您自己的硬件上实时运行(我无法负担 GPU 集群)。这样做暴露了一些问题,其中最重要的是关键字搜索在几年的时间里有一个错误。事实证明,进行适当的评估是重要的。明显的方法需要 23 兆字节。驱动 Python 文章的模型(句子变换器如 all-MiniLM-L6-v2)事实上可以在浏览器中运行。Transformers.js 将下载一个 ONNX 构建并在 WebAssembly 上运行它。我进行了基准测试,在我的笔记本电脑上,量化模型加上其运行时加载时占用大约 23.45 MB,加载并变得可用大约需要两秒钟,然后在 ±18 毫秒内嵌入查询。对于一篇有 14 篇文章的博客来说,23 兆字节嵌入一个搜索框查询有点过于庞大。这大致相当于下载十几张高分辨率照片的字节,以便我们可以拥有精美的搜索。它可以工作,对于某些应用程序来说完全值得,但我不想把它施加于那些点击阅读关于布隆过滤器的内容的人,尤其是在移动连接上。关键是,您并不需要变换器来嵌入搜索查询。您只需要一个足够好的向量,而有一种更便宜的方法来获得一个。静态嵌入模型是一个查找表。技巧是一类叫做 model2vec 的模型(具体的那些叫做“potion”)。它们是从真实的句子变换器中提炼出来的,但结果并不是一个神经网络。它是一个表。以下是模型的整个前向传递。没有简化 — 完全来自库的源代码:ids = tokenize(text) # 分割成子词令牌 ids rows = embedding[ids] # 每个令牌查找一个向量 vector = rows.mean(axis=0) # 对它们进行平均 vector = vector / norm(vector) # 归一化为单位长度 就这些。标记化,为每个标记查找一行,平均,归一化。没有注意力,没有层,没有推理。“运行模型”只是一些数组查找和一个平均值。对于 potion-base-8M,表是 29,528 个标记乘以 256 维的 float32(也就是大约 30 MB),它达到了 MiniLM 检索质量的 81%,同时定义上是一个字典查找。三十兆字节仍然有点多,但表比变换器更容易缩小。构建索引:分块,以及一个永远赚不到它报酬的缓存 嵌入是在构建时生成的。在我的笔记本电脑上,每当我运行 hugo 时,一个 Python 脚本遍历帖子,剥离前言和代码块,将每个帖子分成重叠的约 600 个字符的块,嵌入每个块,并将向量写入用户单击搜索输入框时浏览器下载的文件。分块比我预期的更重要,因为平均那一步。静态嵌入是其令牌向量的均值,如果您将整个 15,000 字符的帖子平均到一个向量,您会得到一个指向“有关软件的通用英语散文”的内容,而不会得到其他太多的东西。稀有而独特的词(如 pydub 或 mmh3 等术语)会被周围成百上千的普通词淹没。将帖子切成较小的块有助于保持这些信号的清晰。我会回来讨论这个,因为事实证明这是某些模型胜过其他模型的关键。我还建立了一个嵌入缓存,以每个块的文本散列为键,因此重新嵌入只触及变化的块。这被证明是浪费时间和令牌。嵌入这个博客的每个块只是几百个查找和一个平均;大约需要十毫秒。我建立了一个缓存来加速一个已经相当瞬时的操作(事实证明 14 篇博客文章并不是很多的数据)。对于 MiniLM 模型而言,嵌入是一个重新

赞助内容

NordVPN Next-gen Antivirus

本站免费、广告极少。如果觉得有帮助,可以请我们喝杯咖啡 —— 任何金额都对持续运营有实际帮助。

请我喝杯咖啡