
1. 为什么我要把大模型塞进浏览器里跑先说结论我做了一个浏览器插件把15亿参数的量化大模型直接跑在浏览器里配合Excel的批量数据处理场景全程本地推理数据不出电脑断网也能用。这不是概念验证是我自己日常在用的工具。事情的起因很朴素。我手头经常要处理一些包含敏感字段的Excel表格比如客户名单、内部报价、人员信息这类东西。以前的做法是把表格导出成CSV写个Python脚本调用在线大模型API做分类、摘要、字段抽取。这套流程跑得通但有两个让我一直不踏实的地方第一数据要离开本机哪怕走的是加密通道心里那道坎过不去第二一旦网络抖动或者API限流批量任务跑到一半挂掉重跑成本很高。后来我试过本地部署方案用Python起一个推理服务再让Excel通过VBA或者加载项去调用。这个方案确实解决了数据不出本机的问题但部署门槛不低——要装CUDA驱动、配Python环境、拉模型权重、处理显存占用换一台电脑就得重来一遍。对我这种经常在不同机器之间切换的人来说太重了。真正的转折点是WebGPU在主流浏览器里逐渐可用。我意识到一件事浏览器本身就是一个跨平台的运行时WebGPU给了它直接访问GPU算力的能力而模型量化技术已经把15亿参数这个量级的模型压缩到了可以接受的体积。这两件事凑在一起意味着我可以把推理能力直接打包进一个浏览器插件里用户装上就能用不需要装任何额外环境。这个项目的核心价值就三点数据不出本机、断网可用、零环境依赖。适合的人群也很明确——经常处理敏感表格但又想用大模型提效的人以及对本地推理、浏览器插件开发感兴趣的技术同学。下面我把整个思路、技术选型、实操步骤和踩过的坑完整拆一遍。2. 整体架构设计与技术选型思路2.1 为什么是浏览器插件而不是桌面应用一开始我认真考虑过做桌面应用用Electron或者Tauri打包。但很快否掉了原因有三个。第一分发成本。桌面应用要处理安装包、签名、自动更新、不同操作系统的兼容性。浏览器插件走应用商店分发更新是自动的用户点一下安装就完事。对于一个我想让更多人低门槛用上本地推理的目标来说插件形态的触达效率高太多。第二与Excel的集成路径。我的核心场景是Excel批量处理而浏览器插件可以通过Office加载项体系或者剪贴板桥接的方式和Excel交互。桌面应用当然也能做但插件在读取当前表格内容、回写结果这个链路上更轻。第三运行时已经内置。浏览器自带JavaScript引擎、自带WebGPU、自带存储API。我不需要自己维护一个运行时环境这省掉了大量工程负担。当然插件形态也有代价主要是内存上限和后台生命周期管理比桌面应用严格。这个后面在实操部分会详细讲怎么绕。2.2 模型选型为什么是15亿参数这个量级模型大小的选择是整个项目最关键的一步。我试过好几个量级最后锁定在15亿参数附近也就是1.5B这个档位。这个决策背后是一套很实际的权衡。参数量太小比如0.5B以下模型在字段抽取、意图分类这类任务上准确率明显不够经常出现答非所问。参数量太大比如7B以上即使做了4bit量化权重文件也在4GB左右浏览器加载和显存占用都会很吃力首次加载时间可能超过一分钟用户体验直接崩掉。1.5B这个档位是个甜点区。做4bit量化之后权重文件大概在1GB上下浏览器可以比较从容地加载。在字段抽取、文本分类、简单摘要、格式转换这些任务上1.5B模型的表现已经够用。我实测下来对于从一段客户描述里抽出公司名、联系人、电话这种结构化抽取任务1.5B模型的准确率能到90%以上配合好的提示词还能再往上提。具体到模型选择我用的是经过指令微调的1.5B级别模型然后自己做了量化转换。这里要强调一点不是所有模型都适合浏览器端。选模型的时候要看它的架构是否对量化友好以及是否有成熟的ONNX或WebGPU推理路径。有些模型虽然参数少但算子不被WebGPU后端支持跑起来会回退到CPU速度慢到没法用。2.3 推理引擎WebGPU加ONNX Runtime Web的组合推理引擎这块我对比过几条路线。纯WebGPU手写算子灵活但工作量巨大而且不同浏览器的WebGPU实现有差异维护成本高。TensorFlow.js的WebGPU后端生态成熟但对我用的这个模型架构支持不够好转换过程中丢算子。最后我选了ONNX Runtime Web的WebGPU执行提供器。这个组合的好处是模型先转成ONNX格式然后用ONNX Runtime Web加载指定WebGPU作为执行后端。ONNX Runtime Web会自动把能放到GPU上的算子放上去不支持的算子回退到WASM。整个链路的兼容性和性能都比较可控。这里有个关键细节量化格式的选择。我最终用的是INT4量化配合分组量化策略。为什么不用INT8因为INT8的模型体积还是偏大加载慢。为什么不用更激进的INT2或INT3因为精度掉得太厉害抽取任务开始出现明显错误。INT4是体积和精度的平衡点实测模型体积压到1GB左右精度损失在可接受范围内。2.4 数据流转设计Excel到模型再到Excel整个数据流是这样的用户在Excel里选中要处理的数据区域通过插件读取到浏览器端插件把每一行或者每一批数据喂给本地模型模型返回处理结果插件再把结果写回Excel。这里有个设计决策值得说批处理还是逐行处理。逐行处理实现简单但每次推理都有固定开销处理几百行数据会很慢。批处理把多行拼成一个提示一次推理处理多行吞吐量高很多。但批处理的代价是提示变长对模型的上下文长度有要求而且一旦某一行出错整批结果可能受影响。我的做法是动态批处理根据当前行的文本长度动态决定一批塞多少行短文本一批塞10到20行长文本一批塞3到5行。这样既保证了吞吐又不会超出上下文限制。这个策略在实操部分我会给出具体的计算逻辑。3. 核心细节解析与实操要点3.1 模型量化转换的完整流程模型量化是整个项目里最容易翻车的一步我在这上面花的时间比写插件本身还多。下面把流程拆细。第一步是拿到原始模型权重。我用的是HuggingFace格式的模型包含config.json、tokenizer相关文件和safetensors权重。这里要注意一定要确认模型的许可证允许你的使用场景商用和非商用差别很大。第二步是转ONNX。这一步用Optimum或者torch.onnx.export都可以。转换的时候要指定opset版本我用的opset 17兼容性比较好。转换命令大致是这样optimum-cli export onnx --model ./original-model --task text-generation ./onnx-model转换完成后会得到一个model.onnx文件和一堆外部数据文件。这时候模型还是FP32的体积很大。第三步是量化。我用ONNX Runtime的量化工具做INT4量化。这里有个坑不是所有算子都支持INT4。量化工具会告诉你哪些算子被量化了哪些被跳过了。如果关键算子被跳过推理速度提升有限。我的做法是先跑一遍量化看报告如果关键矩阵乘法算子没被量化就调整量化配置重新来。from onnxruntime.quantization import quantize_dynamic, QuantType quantize_dynamic( model_input./onnx-model/model.onnx, model_output./quantized/model_int4.onnx, weight_typeQuantType.QInt4, per_channelTrue, reduce_rangeFalse )per_channelTrue这个参数很重要它让量化按通道进行而不是按张量精度损失小很多。reduce_rangeFalse是因为我用的是较新的量化实现不需要缩减范围。量化完成后模型体积从原来的6GB左右压到了1GB出头。这个体积浏览器加载起来就比较舒服了。3.2 浏览器端模型加载与缓存策略模型加载是用户体验的第一道坎。1GB的文件如果每次都从网络拉那体验没法看。所以必须做本地缓存。浏览器端缓存有几个选择Cache API、IndexedDB、Origin Private File System。我最终用的是Cache API加IndexedDB组合。Cache API存模型权重文件因为它是为HTTP响应设计的对大文件友好。IndexedDB存一些元数据和配置。首次加载时插件从服务器拉取模型文件存进Cache API。后续加载直接从缓存读速度取决于本地磁盘IO通常几秒内能完成。这里有个细节Cache API的存储配额。浏览器对每个源的存储有配额限制通常是磁盘可用空间的一定比例。1GB的模型一般没问题但如果用户磁盘快满了可能会被拒绝。所以我在加载前会先检查配额不够的话给用户明确提示。加载过程中还要处理进度反馈。1GB的文件即使从本地缓存读也要一点时间如果界面没有任何反馈用户会以为卡死了。我用的是分块读取加进度条的方式每读完一块更新一次进度。还有一个容易被忽略的点模型加载后的常驻内存管理。模型加载到WebGPU显存后如果插件后台被挂起显存可能被回收再次唤醒时要重新加载。我的处理方式是监听页面的可见性变化在页面即将隐藏时保存状态唤醒时快速恢复。3.3 Excel数据读取与回写的桥接方案插件和Excel之间的数据桥接是这个项目里比较有技巧性的部分。浏览器插件本身不能直接操作Excel中间需要一个通道。我试过几种方案。第一种是通过剪贴板用户在Excel里复制插件读剪贴板处理完再写回剪贴板用户粘贴。这个方案最简单但交互割裂批量处理时很烦。第二种是通过Office加载项体系。Excel支持Web加载项加载项本身就是一个网页可以直接和我的浏览器插件共享同一个本地推理服务。这个方案集成度最高但开发复杂度也高而且加载项在不同Excel版本上的行为有差异。第三种是我最终采用的本地桥接服务加文件监听。插件在本地起一个轻量的桥接进程用Node或者Python都行这个进程负责读写Excel文件插件通过本地回环地址和它通信。用户在Excel里保存文件桥接进程检测到变化读取数据传给插件插件处理完写回文件Excel里刷新就能看到结果。这个方案的好处是解耦。Excel和插件不需要知道对方的存在通过文件这个中间媒介交互。坏处是用户需要多装一个桥接进程但相比装CUDA环境已经轻太多了。数据格式上我用的是JSON作为中间格式。Excel的每一行转成一个JSON对象字段名是表头字段值是单元格内容。处理完再转回表格写回。这里要注意数据类型保持Excel里的数字、日期、文本在转换过程中容易丢失类型信息我在JSON里加了类型标记来保持。3.4 提示词工程让1.5B模型干好结构化任务1.5B模型的能力有限提示词写得好不好直接决定成败。我在提示词上做了大量实验总结出几条对这个小模型特别有效的原则。原则一任务要极度具体。不要跟模型说帮我处理这些数据要说从下面这段文本里抽出公司名称如果没有就填无。任务越具体小模型越不容易跑偏。原则二给例子比讲道理管用。1.5B模型的指令遵循能力有限与其用大段文字描述要求不如给两三个输入输出示例。我用的提示词模板大致是这样任务从文本中抽取公司名称和联系人。 示例1 输入张三来自北京华信科技有限公司电话138xxxx 输出{公司:北京华信科技有限公司,联系人:张三} 示例2 输入李四上海远大集团市场部 输出{公司:上海远大集团,联系人:李四} 现在处理 输入{待处理文本} 输出原则三输出格式要强约束。我要求模型输出JSON并且在提示词里明确说只输出JSON不要有其他内容。即使这样小模型偶尔还是会加解释性文字所以我在解析端做了容错用正则把JSON部分抠出来。原则四控制单次输入长度。1.5B模型的上下文窗口通常不大我一般把单次输入控制在512个token以内。超长的文本先做切分分段处理再合并。这几条原则配合下来在字段抽取任务上1.5B模型的表现比我最初预期的好很多。关键是要接受它的能力边界把任务拆得足够细。4. 实操过程与核心环节实现4.1 开发环境搭建与插件骨架先说环境。我用的开发环境是Node 20加Vite插件基于Manifest V3。为什么用Vite因为它的开发服务器热更新快打包产物干净对插件这种需要频繁调试的场景很友好。插件骨架包含几个部分manifest.json定义权限和入口background service worker处理后台逻辑content script负责和页面交互popup或者side panel提供用户界面。我这个项目因为要处理大量数据界面用的是side panel空间大不遮挡主页面。manifest.json里几个关键权限storage用于缓存模型unlimitedStorage用于突破存储配额限制offscreen用于在后台文档里跑推理因为service worker里不能直接用WebGPU。这个offscreen权限是Manifest V3的一个特性允许创建一个隐藏的文档来执行需要DOM或者特定API的任务WebGPU推理就属于这类。{ manifest_version: 3, name: 本地Excel智能处理, permissions: [storage, unlimitedStorage, offscreen], background: { service_worker: background.js }, side_panel: { default_path: sidepanel.html } }这里有个坑要提醒service worker的生命周期。Manifest V3的service worker会在空闲时被浏览器挂起如果推理任务跑在service worker里跑到一半被挂起就前功尽弃。所以我把推理放在offscreen document里它的生命周期更稳定。4.2 WebGPU推理的初始化与执行推理初始化的核心是创建ONNX Runtime Web的session指定WebGPU执行提供器。import * as ort from onnxruntime-web/webgpu; async function initSession(modelBuffer) { const session await ort.InferenceSession.create(modelBuffer, { executionProviders: [webgpu], graphOptimizationLevel: all, executionMode: parallel }); return session; }executionProviders指定webgpu如果浏览器不支持会自动回退到wasm。graphOptimizationLevel设为all让ONNX Runtime做尽可能多的图优化。executionMode设为parallel允许并行执行独立节点。推理执行的时候输入要先tokenize。我用的是模型自带的tokenizer转成ONNX之后tokenizer部分通常还是用JavaScript实现。输入张量的形状要严格匹配模型要求通常是[batch_size, sequence_length]。async function runInference(session, inputIds, attentionMask) { const feeds { input_ids: new ort.Tensor(int64, BigInt64Array.from(inputIds), [1, inputIds.length]), attention_mask: new ort.Tensor(int64, BigInt64Array.from(attentionMask), [1, attentionMask.length]) }; const results await session.run(feeds); return results; }这里有个性能细节输入张量的创建开销。每次推理都新建Tensor会有开销对于批量任务我复用了张量缓冲区只在形状变化时重建。这个优化在批量处理几千行数据时效果明显。还有一个关键点WebGPU的首次调用延迟。WebGPU在第一次执行时要做管线编译可能耗时几百毫秒到几秒。我的做法是在插件启动时就跑一次空推理做预热把管线编译的延迟提前消化掉用户真正开始处理数据时就是热状态了。4.3 批量处理的动态分批算法前面提到动态批处理这里给出具体实现。核心思路是根据文本长度估算token数然后按token预算分批。function estimateTokens(text) { // 粗略估算中文约1.5字符/token英文约4字符/token const chineseChars (text.match(/[\u4e00-\u9fa5]/g) || []).length; const otherChars text.length - chineseChars; return Math.ceil(chineseChars / 1.5 otherChars / 4); } function dynamicBatch(rows, tokenBudget 400) { const batches []; let currentBatch []; let currentTokens 0; for (const row of rows) { const rowTokens estimateTokens(row.text) 50; // 50是提示词模板开销 if (currentTokens rowTokens tokenBudget currentBatch.length 0) { batches.push(currentBatch); currentBatch []; currentTokens 0; } currentBatch.push(row); currentTokens rowTokens; } if (currentBatch.length 0) { batches.push(currentBatch); } return batches; }tokenBudget我设的是400留出余量给模型输出。这个值可以根据实际模型调整上下文窗口大的可以调高吞吐量会更好。分批之后每一批拼成一个提示一次推理处理整批。解析结果时按行拆分和原始行对应。这里要处理结果行数不匹配的情况模型偶尔会多输出或者少输出我的做法是如果行数不匹配就退化成逐行处理这一批保证结果正确性优先。4.4 结果回写与Excel联动处理完的数据要写回Excel。通过桥接进程写回时我保持了原有的单元格格式和公式。这里有个细节只覆盖数据区域不动表头和公式列。我在读取时就记录了哪些列是需要处理的写回时只更新这些列。写回之后Excel里不会自动刷新用户需要手动刷新或者重新打开文件。为了体验好一点桥接进程可以在写回后发一个信号如果用户装了对应的加载项加载项收到信号自动刷新。没装加载项的话手动刷新也就一下的事。整个流程跑下来处理1000行数据的耗时大概是这样模型加载首次10到20秒之后从缓存加载3到5秒1000行数据分批推理每批10到20行总共50到100批每批推理200到500毫秒总计30到60秒。加上读写文件的时间整体在1到2分钟内完成。这个速度对于本地推理来说是可以接受的而且全程数据不出本机。5. 常见问题与排查技巧实录5.1 模型加载失败与显存不足这是最常见的问题。表现是插件启动后卡在加载界面或者报out of memory。排查思路分几步。先看浏览器是否支持WebGPU在地址栏输入chrome://gpu查看WebGPU状态。如果显示不支持要么是浏览器版本太老要么是显卡驱动太旧。WebGPU对驱动版本有要求太旧的驱动会直接禁用。如果WebGPU支持但加载失败大概率是显存不够。1GB的模型加上推理时的中间张量峰值显存占用可能在1.5GB到2GB。集成显卡或者老独显可能扛不住。这时候可以尝试降低批处理大小减少同时驻留的张量。或者换用更小的量化版本比如从INT4降到INT3精度会掉一些。还有一个隐蔽的坑多个标签页同时加载模型。每个标签页是独立的上下文各自加载一份模型显存直接翻倍。我的插件做了单例控制检测到已有实例在运行就复用不重复加载。5.2 推理结果不稳定与格式错误1.5B模型输出不稳定是常态尤其是提示词没写好的时候。典型表现是该输出JSON的时候输出了大段解释或者字段名对不上或者干脆胡言乱语。我的排查和解决经验是这样的。首先检查提示词是不是任务描述不够具体是不是没给示例。加上两三个示例通常能解决大部分问题。其次检查输入是不是有超长文本没切分导致模型注意力被稀释。再就是检查温度参数推理时的temperature设低一点比如0.1到0.3输出会更确定。如果这些都做了还是不稳定那就是模型能力边界到了。这时候要么换更大的模型但会牺牲加载速度要么把任务拆得更细。我遇到过从一段话里同时抽公司名、人名、电话、地址这种多字段抽取1.5B模型经常漏字段。拆成四次单字段抽取每次只抽一个准确率立刻上来了。用工程手段弥补模型能力比硬堆模型参数更划算。5.3 Excel桥接进程通信异常桥接进程和插件之间的通信偶尔会断。表现是插件显示等待Excel数据一直不结束或者写回后Excel没变化。排查的时候先确认桥接进程是否在运行。我遇到过用户杀毒软件把桥接进程当可疑程序拦截的情况加白名单就好。再确认端口是否被占用桥接进程默认用的端口如果被其他程序占了通信就失败。我的做法是启动时自动探测可用端口把端口号写到一个约定位置插件从那里读。还有一个坑是文件锁。Excel打开文件时会加锁桥接进程如果尝试在Excel打开状态下写入可能失败。我的处理是检测文件锁状态如果被锁就提示用户先关闭Excel或者写入一个临时文件让用户手动替换。5.4 常见问题速查表问题现象可能原因排查方法解决方案插件卡在加载界面WebGPU不支持或显存不足查看chrome://gpu状态更新浏览器和驱动或降低量化等级推理报out of memory批处理过大或标签页重复加载查看显存占用减小批大小确保单例运行输出格式错乱提示词不具体或温度过高检查提示词和参数加示例降低temperature字段抽取漏项任务过于复杂超出模型能力对比输入输出拆分为单字段抽取任务桥接通信失败进程未启动或端口冲突检查进程和端口重启桥接自动探测端口写回后Excel无变化文件被锁或未刷新检查文件锁状态关闭Excel后写入或手动刷新处理速度极慢回退到CPU推理查看执行提供器日志确认WebGPU可用检查算子支持5.5 几个我踩过的坑和独家技巧第一个坑是tokenizer的兼容性。模型转ONNX的时候tokenizer有时候不会一起转需要单独处理。我一开始用了一个通用的JavaScript tokenizer结果分词结果和模型训练时不一致输出全是乱的。后来老老实实用模型自带的tokenizer配置用tokenizers库的WASM版本才对齐。第二个坑是WebGPU的精度问题。WebGPU的浮点运算在某些显卡上精度和CPU不一致导致模型输出有细微差异。大部分时候没影响但在做数值敏感的抽取时偶尔会出错。我的应对是在关键任务上保留一个CPU回退路径WebGPU结果可疑时用CPU重跑一遍确认。第三个技巧是模型预热。前面提过但值得再强调。插件启动后立刻跑一次短推理把管线编译、内存分配这些一次性开销提前消化。用户感知到的首次推理延迟能从几秒降到几百毫秒。第四个技巧是结果缓存。同样的输入如果之前处理过直接返回缓存结果不重复推理。这在处理有重复行的表格时特别有用能省掉大量计算。缓存用IndexedDB存key是输入的哈希值。第五个技巧是渐进式加载。模型文件1GB如果等全部加载完再让用户操作体验不好。我的做法是先把模型分成几个部分加载完第一部分就允许用户开始处理简单任务后续部分在后台继续加载。这个实现起来复杂一些但对大模型的加载体验提升明显。6. 性能优化与扩展方向6.1 推理速度的进一步压榨WebGPU推理的速度瓶颈通常在两个地方数据传输和算子效率。数据传输指的是CPU和GPU之间的张量拷贝这个开销在批量小时占比很高。优化方法是尽量让数据留在GPU上减少往返。ONNX Runtime Web在这方面做了不少优化但仍有空间。算子效率方面INT4量化后的矩阵乘法在WebGPU上的实现质量参差不齐。我试过不同的量化配置发现分组大小group size对速度影响很大。分组太小反量化开销大分组太大精度掉。我最后用的分组大小是128速度和精度的平衡比较好。还有一个优化点是KV Cache复用。对于生成式任务如果多次推理的提示前缀相同可以复用KV Cache避免重复计算。这个在批量处理相似结构的行时特别有效。ONNX Runtime Web对KV Cache的支持还在完善中我目前是用手动方式实现的把公共前缀的KV缓存下来。6.2 从Excel扩展到更多场景这个项目的核心能力是浏览器内本地推理加结构化数据处理Excel只是第一个落地场景。同样的架构可以扩展到很多地方。比如本地文档处理。把PDF、Word文档读进来用本地模型做摘要、抽取关键信息、翻译全程不出本机。这个场景对隐私敏感的用户很有吸引力。再比如邮件批量处理。读取本地邮件客户端的数据用模型做分类、优先级排序、自动回复草稿。同样是不出本机的本地推理。还有代码辅助。在浏览器里的在线编辑器或者本地IDE的Web版本里用本地模型做代码补全、注释生成、bug解释。1.5B模型在代码任务上能力有限但做一些简单的补全和解释是够的。这些扩展的共同点是数据敏感、需要结构化处理、对实时性要求不是极致。这正是浏览器本地推理的甜点区。6.3 模型更新的平滑升级策略模型是会迭代的新版本可能精度更高或者体积更小。插件需要支持模型更新但又不能每次更新都让用户重新下载1GB。我的策略是版本化加增量更新。模型文件按版本号存储新版本发布时只下载变化的权重块。这需要模型文件本身支持分块我在转换时就按层切分好了。更新时对比版本清单只拉差异部分。实测下来小版本更新通常只需要下载几十MB。更新过程中旧版本继续可用新版本下载完并验证通过后再切换。这样用户不会遇到更新到一半不能用的情况。7. 一些实际使用中的体会这个项目从想法到能用前后折腾了大概两个月。最大的体会是本地推理的瓶颈往往不在模型本身而在工程细节。模型量化、加载缓存、显存管理、批处理策略、结果解析每一个环节都有坑每一个坑都可能让整个方案不可用。另一个体会是要接受小模型的能力边界。1.5B模型不是万能的它在结构化抽取、分类、简单摘要上表现不错但在需要复杂推理、长文本理解、多轮对话的任务上力不从心。把任务设计得匹配模型能力比一味追求大模型更实际。很多时候把一个大任务拆成几个小任务用小模型分别处理效果比用一个大模型硬扛更好。还有一点关于隐私的价值。当数据处理全程在本机完成用户的心理负担是完全不同的。我有些用户之前对在线AI处理敏感数据很抗拒换成这个本地方案后使用频率明显上来了。隐私不只是技术问题也是心理问题本地推理解决的是后者。最后分享一个实用建议如果你也想做类似的东西先从最小的可用版本开始。不要一上来就追求完美的模型、完美的性能、完美的界面。先让读取Excel、本地推理、写回Excel这个最小闭环跑通哪怕模型小一点、速度慢一点。闭环跑通之后再逐个环节优化。我见过太多项目死在追求完美上其实先跑起来比什么都重要。