在浏览器中运行 RAG(使用小语言模型)

Lobsters Hottest 工具

摘要

作者在博客中演示了如何完全在浏览器端运行 RAG:用静态嵌入检索段落,再通过 transformers.js 在 WebGPU/WASM 上运行小型模型(如 Qwen3-0.6B、SmolLM2-360M)生成两句话摘要,全程数据不出本地。文章还评估了这些小模型的忠实度局限。

<p><a href="https://lobste.rs/s/nsjjo3/running_rag_with_small_language_model">评论</a></p>
查看原文
查看缓存全文

缓存时间: 2026/10/13 15:59

# 在浏览器中用(小型)语言模型运行 RAG 来源:https://bart.degoe.de/rag-with-a-small-language-model-in-the-browser/ 几周前,MicroLLM lab (https://stateofutopia.com/experiments/microllmlab/) 出现在了 Hacker Newsletter (https://buttondown.com/hacker-newsletter/archive/812/) 上。它展示了如何在浏览器中,在用户自己的 GPU(或 CPU)上运行小型语言模型(2500 万到 3.6 亿参数)¹ (https://bart.degoe.de/rag-with-a-small-language-model-in-the-browser/#fn:1)。不需要服务器、不需要 API 密钥、不需要注册账号。非常酷,这让我想到了在为这个博客添加的语义搜索 (https://bart.degoe.de/semantic-search-in-your-browser/) 之后还能做什么:语义搜索已经为 RAG 中的 "R" 提供了支持² (https://bart.degoe.de/rag-with-a-small-language-model-in-the-browser/#fn:2),而且完全在浏览器中运行,只需要一个 4 MB 的查找表。如果有一个微型模型,同样在浏览器中运行,阅读这些片段并回答问题呢? 于是我做了这个:"ask my blog" (https://bart.degoe.de/ask/)³ (https://bart.degoe.de/rag-with-a-small-language-model-in-the-browser/#fn:3)。搜索找到四个最相关的段落,然后一个小型语言模型在你的浏览器中运行,将它们总结成两句话。你输入的任何内容都不会离开你的设备。而且,因为"一个小型模型来总结内容"这句话恰好掩盖了很多编造的胡话,我测量了它实际上有多忠实于我写的内容。结果发现小型模型确实有些局限性。 在 WebGPU 上运行的 Ask my blog:先是 Qwen3-0.6B(已缓存)在回答,然后切换到 SmolLM2-360M 时开始下载。 ## 工作原理 该页面是一个检索增强生成(RAG),每个环节都在客户端运行: 1. **检索。**你的问题经过与主页相同的静态嵌入搜索,但结果不会汇总为文章,而是保留得分最高的四个*段落*。 2. **门槛。**如果最高分段落低于阈值,则完全不会调用模型,页面会说在博客上找不到相关内容。思路是:如果用户问的东西根本没有数据支持,就不要费劲了。关于为什么这只起到一半作用,下面再详述。 3. **提示。**段落(带编号),加上一个系统提示:最多用两句话回答,只使用这些段落,如果段落没有回答问题就明说。 4. **生成。**一个 Web Worker 使用 transformers.js (https://huggingface.co/docs/transformers.js) 在 WebGPU 上运行模型,如果你的浏览器不支持 WebGPU,则通过 WebAssembly 在 CPU 上运行,并将回答流式输出到页面。一旦生成了两个完整的句子就会停止。 段落会先以卡片形式显示在页面上,摘要则显示在它们上方。你可以搜索而完全不用加载模型;只是得到段落。加载模型需要明确点击,因为这意味着从 Hugging Face 下载几百 MB 的内容(只需一次;浏览器会缓存)。还有一个清除缓存的按钮。 下载指示器:每个方块代表模型的 4 MB ## 选哪个模型? 我想要一个对读者来说下载负担合理的东西:几百 MB,而不是几个 GB(万一有人在公交车上看这篇文章呢)。有浏览器可用(ONNX)构建的候选模型包括 SmolLM2(1.35 亿 (https://huggingface.co/HuggingFaceTB/SmolLM2-135M-Instruct) 和 3.6 亿 (https://huggingface.co/HuggingFaceTB/SmolLM2-360M-Instruct) 参数)、Gemma 3 270M (https://huggingface.co/onnx-community/gemma-3-270m-it-ONNX)、LFM2.5-350M (https://huggingface.co/onnx-community/LFM2.5-350M-ONNX)、Qwen2.5-0.5B (https://huggingface.co/onnx-community/Qwen2.5-0.5B-Instruct) 和 Qwen3-0.6B (https://huggingface.co/onnx-community/Qwen3-0.6B-ONNX)。作为参考,我加了两个永远不会在页面上发布的模型:Gemma 4 E2B (https://huggingface.co/onnx-community/gemma-4-E2B-it-ONNX),Google 新的 Gemma 4 系列中最小的,下载量为 3.1 GB;以及通过 API 调用的 Claude Haiku 5.5 (https://www.anthropic.com/claude-haiku-5-5),即"花钱找人"的方案。 在运行任何东西之前就有一个意外发现:对于这些模型中的好几个,"4-bit"(q4)构建反而比 4-bit 加 16-bit 浮点(q4f16)的构建*更大*,因为 q4 导出保留了全 32 位精度的嵌入表。SmolLM2-360M 的 q4 版本是 390 MB,q4f16 版本是 275 MB,而输出完全相同。下面所有数据都使用 q4f16。 ## 评测 我复用了为语义搜索文章做的评测中的 30 个问题 (https://bart.degoe.de/semantic-search-in-your-browser/),改写成了实际的提问形式("博客是怎么用 Lunr 添加搜索的?"),并额外加了 15 个这个博客肯定*不能*回答的问题。其中几个偏差很远("如何在家烤酸面包?"),但大多是精心设计的"擦边"问题,目的是诱导模型预测听起来合理但错误的 token:"我应该买什么 GPU 来本地运行 Llama 70B?"、"BM25 排序相比 TF-IDF 有什么改进?"、"如何微调 Kokoro 用我自己的声音说话?"。博客*讨论过*这些或类似的话题,但并没有这些问题的答案。 每个模型都收到相同的四个段落和相同的提示,然后由 Opus 5.5 根据那些段落对每个回答进行评判,分为**忠实**(每个论断都来自段落)、**无依据**(至少一个论断不是,即使它碰巧是真的)或**非回答**。我自行标注了其中 20 个评级,没有看评判者的结论;在回答是否忠实这个问题上,我们 20 个中一致了 16 个⁴ (https://bart.degoe.de/rag-with-a-small-language-model-in-the-browser/#fn:4)。Claude 也生成了问题集,我进行了审核。 忠实回答数与下载大小 30 个可回答问题中被忠实回答的比例,按模型划分,横轴为对数尺度的下载大小。350 MB 以内的模型得分 0 到 3(满分 30);Qwen3-0.6B(579 MB)15;Gemma 4 E2B(3.1 GB)28;Claude Haiku 5.5 通过 API 27。 在 350 MB 预算内:0%、25%、50%、75%、100% 忠实回答(满分 30),下载大小(对数尺度)100 MB、300 MB、1 GB、3 GB、API SmolLM2-135M:忠实回答 0/30;对博客未覆盖的 15 个问题中的 14 个编造了答案(下载 120 MB) SmolLM2-360M:忠实回答 3/30;对博客未覆盖的 15 个问题中的 15 个编造了答案(下载 275 MB) Gemma 3 270M:忠实回答 3/30;对博客未覆盖的 15 个问题中的 13 个编造了答案(下载 293 MB) LFM2.5-350M:忠实回答 2/30;对博客未覆盖的 15 个问题中的 14 个编造了答案(下载 258 MB) Qwen3-0.6B:忠实回答 15/30;对博客未覆盖的 15 个问题中的 5 个编造了答案(下载 579 MB) Gemma 4 E2B:忠实回答 28/30;对博客未覆盖的 15 个问题中的 0 个编造了答案(下载 3131 MB) Claude Haiku 5.5:忠实回答 27/30;对博客未覆盖的 15 个问题中的 1 个编造了答案 Haiku 5.5 · 27/30 | 4 个不超过 293 MB 的模型 0 到 3/30 | Qwen3-0.6B · 15/30 | Gemma 4 E2B · 28/30 30 个可回答问题中忠实回答的数量,对比下载大小。悬停圆点查看详情。 | 模型 | 下载 | 忠实/30 | 编造/15 | 是否在 350 MB 内 | |---|---|---|---|---| | SmolLM2-135M | 120 MB | 0 | 14 | ✓ | | LFM2.5-350M | 258 MB | 2 | 14 | ✓ | | SmolLM2-360M | 275 MB | 3 | 15 | ✓ | | Gemma 3 270M | 293 MB | 3 | 13 | ✓ | | **更大的下载** | | | | | | Qwen3-0.6B(默认) | 579 MB | 15 | 5 | | | Gemma 4 E2B | 3.1 GB | 28 | 0 | | | **托管 API** | | | | | | Haiku 5.5(API) | | 27 | 1 | | 结果发现,350 MB 以下没有任何现成模型可以用。它们不是稍微差一点;它们三十次中最多**忠实三次**,而且它们几乎乐于回答你扔给它们的任何问题。被问到应该买什么 GPU 来运行 Llama 70B,而手上只有四段关于嵌入和 MacBook 上编码 agent 的段落: > "……运行 Llama 70B 本地的最佳选择可能是 NVIDIA GTX 960 或 GTX 970。……它们还提供高内存带宽,非常适合处理像 Llama 70B 所需的 640 万份文档这样的大量数据。"(SmolLM2-360M) > "……高性能显卡(GPU),如 NVIDIA A100 系列(例如 RTX 3090、RTX 3080、3070、3080、3070M、3080M)"(Gemma 3 270M) > "……流行的选项包括 RTX 3090、RTR 5900,或更新一代如 TensorRT 60+ 以获得更好的计算效率。"(LFM2.5-350M) 顺便一提,Gemma 4 E2B 正确地判断出:"所提供的段落中不包含关于你应该购买什么 GPU 来本地运行 Llama 70B 的信息。" 干得好,Google。 即使在博客*确实*回答的问题上,小型模型也很容易搞乱。以下是 LFM2.5 对我关于 Bloom filter 的文章 (https://bart.degoe.de/bloom-filters-bit-arrays-recommendations-caches-bitcoin/) 最基本的问题的回答,在我笔记本的 GPU 上以每秒 73 个 token 的速度生成: LFM2.5-350M 将 Bloom filter 解释为带有"true negative"的哈希表 快速、流畅,但错误。Bloom filter 不是哈希表:它是一个位数组,一个元素只有当它的全部 *k* 个哈希函数命中的位都被置位时,才算"可能在集合中"。最后一个分句实际上是正确的(缺失的位意味着该元素从未被添加过,这就是 Bloom filter 不能给出假阴性的原因),但其余的都是编的。公平地说,搜索没有把解释 Bloom filter 工作原理的那部分内容交给模型;它拿到的四个段落是关于用例和交互式 demo 的。摘要只能和它读到的段落一样好。看来我的小型搜索引擎在相关性评分上还有工作要做。 Qwen3-0.6B 是有趣的一个。579 MB 相当大,而且超出了我给自己设定的预算。它的忠实率是50%,基本就是抛硬币。但它是能抛硬币而不是几乎必然失败的最小模型,所以 "ask my blog" (https://bart.degoe.de/ask/) 默认加载的就是它。你也可以选择三个更小的模型,自己看看一个未经微调的 300 MB 模型会怎么处理你的问题⁵ (https://bart.degoe.de/rag-with-a-small-language-model-in-the-browser/#fn:5)。 ## 小型模型的死循环 第一次运行小型模型时,它们的回答大多不是错,而是卡住了。SmolLM2-135M 被问到如何为网站添加 OpenSearch 支持: > "1. 自定义搜索:从你的 URL 栏搜索 2. 从你的 URL 栏搜索 3. 从你的 URL 栏搜索 4. 从你的 URL 栏搜索 [1] 自定义搜索:从你的 URL 栏搜索……" Gemma 3 270M 对 GPU 问题的回答是 `I'5555555555555555555555……`,一直输出到 token 用完。这就是当你总是选最可能的下一个 token 而没有任何东西阻止重复时会发生的事:微型模型会陷入一个坑里爬不出来(我在玩 MicroLLMLab 时也经常看到这个现象)。简单的修法是加一个重复惩罚(这里设为 1.15,会略微抑制已经出现过的 token)。这不能解决忠实度或质量问题;模型只是换成了更长、更多样的胡话。上面所有数字都是在惩罚设为 1.15 的情况下得出的。 ## 用相关性门槛拦住明显的胡话 相关性门槛似乎是解决编造答案的廉价方案:如果搜索找不到任何相关内容,就不要调用模型。这对偏差很远的问题有效;"如何烤酸面包?"与博客上最相关的段落得分只有 0.25,0.45 的阈值就能拦住(这很好,因为 Gemma 3 的回答是一个面包食谱)。但那些"擦边"问题的得分是 0.54 到 0.63,比几个博客真正能回答的问题得分还*高*,因为它们命中了同一话题的文章。要拦住这些,阈值就得同时拒绝掉 30 个真实问题中的 13 个。 所以门槛只能拦住明显跑题的问题,其余的全靠模型自己。这就是为什么模型规模在这里很重要的真正原因:Gemma 4 和 Haiku 会说"这些段落没有涵盖这个",而小型模型从来不会。 ## 值得租 GPU 或付费调用 API 吗? 通常来说,值得。以目前的价格(感谢竞争),几乎从不值得自建,除非你有充分的理由不这么做(比如合规、隐私、气隙部署等)。但作为一次思维实验:3.1 GB 的 Gemma 4 E2B 在质量上与 Haiku 不相上下,那么让服务器运行它、而不是让读者下载,需要花多少钱?最便宜的合理云 GPU——Google Cloud 上的 NVIDIA L4——大约每小时 $0.71⁶ (https://bart.degoe.de/rag-with-a-small-language-model-in-the-browser/#fn:6),持续运行一个月是**$516**。按这个博客一个月的读者量,假设每个人都问一个问题,那每个问题就超过一美元。 而 Haiku 回答全部 45 个评测问题只花了**$0.006**。按每月 500 个问题算(一个梦想),大约花费七美分。换句话说,如果你有真正的使用场景,先从 Haiku 开始吧⁷ (https://bart.degoe.de/rag-with-a-small-language-model-in-the-browser/#fn:7)。 ## 越短越安全 如果 Qwen3 的两句话回答只有一半是对的,让它多说点会不会有帮助?我用 Qwen3-0.6B 和 Gemma 4 E2B 做了一个快速实验,分别允许 2、4 或 6 句话。 忠实回答数与允许的回答长度 当模型最多可用 2、4 或 6 句话时的忠实回答数(满分 30)。Gemma 4 E2B:28、26、24。Qwen3-0.6B:15、9、5。 Gemma 4 E2B 与 Qwen3-0.6B,0%、25%、50%、75%、100% 忠实回答(满分 30),模型允许的句子数 2、4、6 Gemma 4 E2B,最多 2 句:28/30 忠实;对 15 个博客未覆盖的问题中的 0 个编造了答案 Gemma 4 E2B,最多 4 句:26/30 忠实;对 15 个博客未覆盖的问题中的 0 个编造了答案 Gemma 4 E2B,最多 6 句:24/30 忠实;对 15 个博客未覆盖的问题中的 0 个编造了答案 Qwen3-0.6B,最多 2 句:15/30 忠实;对 15 个博客未覆盖的问题中的 5 个编造了答案 Qwen3-0.6B,最多 4 句:9/30 忠实;对 15 个博客未覆盖的问题中的 7 个编造了答案 Qwen3-0.6B,最多 6 句:5/30 忠实;对 15 个博客未覆盖的问题中的 6 个编造了答案 当模型最多可用 2、4 或 6 句话时的忠实回答数(满分 30)。 两个模型在获得更多空间后忠实度都*下降*了,但更小的模型是断崖式下跌:Qwen3 从 15 降到 9 再降到 5。每一句额外的话都是一次新的幻觉机会。在六句话时,它甚至干脆不再试图回答 GPU 问题,而是热心地把每段都总结了一遍,最后得出"答案取决于用户需要高性能 GPU 还是标准 GPU。" Gemma 4 也略有下降(28、26、24),但它几乎不使用额外空间(允许 6 句时大约只写 2.8 句),而且从未对博客未覆盖的问题编造答案。顺便为 Gemma 4 点个赞,这个模型系列真的很棒。这是微调的一个好选择。 ## 接下来 下一步是让小型模型在这一项任务上做得更好。一个 300 MB 的模型不需要什么都知道;它需要读四段内容,然后要么总结它们,要么说它们没有回答问题。这听起来像是一个微调的问题,也许会写一篇后续文章。 另一个想法是主页的搜索框:如果你已经在"ask my blog" (https://bart.degoe.de/ask/) 上加载了一个模型,主页可以悄悄地利用它来改进搜索,而不需要自己下载任何东西。在此之前:向"ask my blog"提问吧 (https://bart.degoe.de/ask/),在相信摘要之前先看看段落。

相似文章

@GitHub_Daily: 想搞懂 RAG 到底怎么回事,网上的教程不是跳步骤就是直接调云端 API,中间过程看不见。 RAG from Scratch 把整条链路拆成十来个小实验,每一步都用本地模型跑,没有黑盒。 从文本分块、向量化、检索到最终生成,代码都摆在面前…

X AI KOLs Timeline

介绍开源项目 RAG from Scratch,通过分步骤的本地代码实验完整拆解 RAG 链路,涵盖文本分块、向量化、检索、重排序与查询重写等进阶策略,帮助开发者从底层理解 RAG 实现。

一个 80B MoE 模型(Qwen3-Next,48 GB GGUF)在 Chrome 标签页中回答问题:使用 llama.cpp 的 WebGPU 后端,专家从磁盘按需分页加载

Reddit r/LocalLLaMA

作者将上周的 MoE 专家按需从磁盘流式加载的方案接入 llama.cpp 的 WebGPU 后端,使 80B MoE 模型(Qwen3-Next,48GB GGUF)也能在 Chrome 标签页中运行,无需为每个模型手写引擎。该方案新增了 ggml-webgpu 的缓冲类型、slot 映射的 MUL_MAT_ID 着色器和驻留逻辑,实测在 M4 Pro MacBook 上以 2.9 tok/s 解码 80B 模型,较小模型可达 22 tok/s。