
1. 背景与核心概念最近在验证大模型效果时我经常卡在一个很尴尬的环节模型已经训练好但要跑一个标准 Benchmark需要GPU服务器、CUDA 环境、Python 依赖、数据集下载一套流程下来半天就没了。如果只是临时验证几个样例成本非常高。于是我把目光投向浏览器。现代浏览器的 WebGPU、WASM 等能力越来越强完全可以在本地加载真实模型执行推理甚至完成一套小规模的 Benchmark 评测。Trunchbull 正是这个方向上一个很值得关注的项目它允许你在浏览器中直接运行真实模型并针对任意 Benchmark 做评测。这篇文章会围绕 Trunchbull 做一次从概念到实践的完整梳理包括它解决什么问题、浏览器端模型评测的原理、一个可以运行的实战示例、常见坑点以及工程建议。如果你对“模型评测”“浏览器推理”“前端AI”感兴趣这篇文章应该能提供一些参考。1.1 浏览器端模型评测是什么传统意义上的模型评测是把模型部署到服务器上用脚本加载测试集逐条跑推理再计算准确率、F1 等指标。例如用 HellaSwag、MMLU、GLUE 这些公开基准来比较不同模型的综合能力。而 Trunchbull 的思路是把“加载模型”和“跑 Benchmark”这两个步骤全部放到浏览器里完成。用户在浏览器打开一个页面选择或者上传模型文件再选择一份评测数据集页面内部会调用浏览器的推理引擎执行模型前向计算最后在页面里展示指标结果。这种模式听起来很新本质上是“本地推理 前端评测”。好处很明显不需要配置服务器和 GPU 环境。模型权重和数据不出浏览器隐私性更好。评测结果可以一键生成报告方便分享。对模型进行快速对比时非常方便。1.2 Trunchbull 的设计思路Trunchbull 这个项目名字挺有意思如果没有记错它来自《玛蒂尔达》里的特伦奇布尔小姐那位老师以严格和强势著称。用这个名字来命名一个评测工具大概是希望评测足够严格、足够可靠。从项目标题 “run real models against any benchmark in your browser” 来看核心关键词有三个real models真实模型不是玩具不是模拟计算。any benchmark任意评测集可以自由定义。in your browser全程在浏览器内完成。也就是说Trunchbull 更像是一个“评测工作台”把模型推理引擎、评测数据解析、指标计算、结果展示这些能力整合到一起。用户不需要写太多 Python 代码直接在浏览器里完成一次模型能力评估。1.3 为什么开发者需要关注它如果你是前端开发者它代表了 AI 应用的一种新形态模型推理不再是后端专属能力浏览器也能承担一部分推理任务。这意味着 AI 能力可以下沉到端侧降低部署成本。如果你是算法工程师它提供了一种快速评估模型的方式。比如训练过程中想快速看某个 checkpoint 在 mini benchmark 上的表现不需要启动完整的评测环境直接在浏览器里拖入模型文件就能看结果。如果你是开源爱好者这类项目也值得关注因为它把“模型评测”这个传统上很重的工程问题做成了轻量化、可视化的网页应用。2. 环境准备与版本说明在开始实战之前我们先明确本地开发环境和运行环境。由于 Trunchbull 本身是基于浏览器运行的我们的准备工作也主要集中在浏览器、Node.js 工具链和模型格式上。2.1 浏览器要求浏览器端推理目前主要依赖两个能力WebAssemblyWASM提供接近原生的计算性能。WebGPU利用 GPU 做大规模并行计算。如果只是跑一个小模型WebAssembly 足够如果跑 7B、13B 这样的大模型建议使用支持 WebGPU 的浏览器。目前主流的 Chrome、Edge 都支持 WebGPUFirefox 也在逐步推进。建议使用最新稳定版 Chrome 或 Edge并开启硬件加速。# 检查浏览器是否支持 WebGPU # 在地址栏输入 about://gpu # 然后查看 WebGPU 一栏是否显示 Enabled如果没有硬件加速WebGPU 可能无法使用推理会退回到 WASM速度会慢很多。2.2 Node.js 环境浏览器端推理不强制依赖 Node.js但本地开发时需要用到工程化工具。例如我们想用 ES Module 方式引入依赖或者希望用 Vite 做开发服务器就需要安装 Node.js。建议使用 Node.js 18 或更高版本npm 9 以上。node -v npm -v本文示例不依赖复杂构建工具使用浏览器原生 ES Module 即可。2.3 模型格式准备浏览器无法直接加载 PyTorch 的 .pt 文件或 Hugging Face 的 safetensors 大文件通常需要转换成浏览器推理引擎支持的格式。目前浏览器推理最常用的格式有两类ONNX通过 ONNX Runtime Web 运行。GGUF通过 llama.cpp 的 WASM 构建运行。如果你用的是 Transformers.js它可以直接从 Hugging Face 加载 ONNX 格式模型也可以借助命令行工具将 PyTorch 模型转换为 ONNX。这里以最常用的 Transformers.js 为例它的模型转换过程非常简单npx huggingface/transformerslatest convert --quantize转换完成后会生成一个 ONNX 模型目录包含 model.onnx 和 config.json 等文件。本文实战部分会直接使用 Transformers.js 加载一个已经转好的模型省去本地转换步骤。3. 核心原理拆解在动手写代码之前我们有必要理解浏览器端模型评测的底层原理。明白原理之后遇到问题才知道从哪里排查。3.1 浏览器推理的三种引擎浏览器本身不直接认识 PyTorch 或 TensorFlow 模型需要通过“翻译层”来执行计算。常见的引擎有三种第一种是 ONNX Runtime Web。它把 ONNX 模型编译成 WebAssembly 或 WebGPU 指令在浏览器里执行。ONNX Runtime 在 CPU 上跑得很稳在 WebGPU 环境下可以调用 GPU 做矩阵运算。第二种是 Transformers.js。它本质上是 Hugging Face Transformers 的 JavaScript 版本底层可以调用 ONNX Runtime Web但封装了更多高层 API。我们可以直接用 pipeline 方式加载模型不用关心张量运算细节。第三种是 GGML / llama.cpp 的 WASM 构建。它主要用于运行 GGUF 格式的 LLM支持 CPU/GPU 混合推理比如 web-llm 这类项目就是基于它的思路。Trunchbull 这类项目大概率不是自己从头写推理引擎而是选择 ONNX Runtime Web 或 Transformers.js 作为底层然后在上层封装评测流程。3.2 评测流程四步走一次浏览器端模型评测无论底层用哪个引擎流程都可以拆成四步第一步加载模型。浏览器下载模型文件并交给推理引擎初始化。由于模型文件可能很大这个过程通常会有进度条。第二步加载评测数据。评测数据的格式一般是 JSON 或 JSONL包含若干条输入和标准答案。例如对于文本分类任务每一条数据可能是“text”和“label”字段。第三步执行推理。将评测数据逐条送入模型得到预测结果。这一步是性能瓶颈模型越大、样本越多耗时越长。第四步计算指标。将预测结果与标准答案对比计算准确率、F1、困惑度等指标。如图所示加载模型 - 加载数据集 - 批量推理 - 指标统计3.3 评测指标怎么算不同的任务有不同的指标。Trunchbull 如果要支持“any benchmark”就必须内置多种指标计算逻辑。最常见的指标是准确率 Accuracy用于分类任务。公式很简单预测正确的样本数除以总样本数。const accuracy correct / total;更复杂一点的指标是 F1 Score用于文本分类、命名实体识别等不平衡数据集。需要计算查准率 Precision 和查全率 Recall。const precision truePositive / (truePositive falsePositive); const recall truePositive / (truePositive falseNegative); const f1 2 * ((precision * recall) / (precision recall));对于生成式任务可能会用到 BLEU、ROUGE 等指标。这类计算在浏览器里也能实现但需要注意中文分词和大小写归一化否则指标会偏低。4. 完整实战在浏览器里跑一个真实模型评测下面我们来写一个最小可运行的示例。这里不直接依赖某个固定版本的 Trunchbull API而是用 Transformers.js 实现同样的思路在浏览器里加载一个真实模型对一组测试样例做推理并计算准确率。这样做的好处是代码可以实际运行同时你也能理解 Trunchbull 这类工具背后的核心逻辑。4.1 创建项目结构先在本地创建一个目录并在里面创建最基本的 HTML 和 JS 文件。trunchbull-demo/ ├── index.html ├── eval.js └── package.json由于我们使用原生 ES Module不需要构建工具但需要启动一个静态服务器。可以用 npm 安装一个简单的服务器。在 package.json 中写入{ name: trunchbull-demo, version: 1.0.0, type: module, scripts: { start: npx serve . } }然后在终端执行npm install npm start浏览器访问 http://localhost:3000 即可。4.2 编写浏览器入口页面创建一个 HTML 页面页面中显示评测结果区域并通过 ES Module 引入 eval.js。!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 titleTrunchbull 浏览器评测示例/title style body { font-family: -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif; max-width: 800px; margin: 40px auto; padding: 0 16px; line-height: 1.6; background: #f8f9fa; color: #333; } .card { background: #fff; border-radius: 8px; padding: 24px; box-shadow: 0 1px 6px rgba(0, 0, 0, 0.08); margin-bottom: 24px; } button { padding: 10px 16px; font-size: 16px; border: none; border-radius: 6px; background: #2b6cb0; color: #fff; cursor: pointer; } button:disabled { background: #a0aec0; cursor: not-allowed; } pre { background: #edf2f7; padding: 12px; border-radius: 6px; overflow-x: auto; } /style /head body div classcard h1Trunchbull 浏览器评测 Demo/h1 p这个页面会加载一个情感分类模型并对一组测试文本做评测。/p button idrunBtn开始评测/button /div div classcard h2评测结果/h2 pre idresult点击按钮后开始加载模型并执行评测。/pre /div script typemodule src./eval.js/script /body /html4.3 编写评测逻辑在 eval.js 中我们需要完成以下事情从 Hugging Face 加载一个文本分类模型。准备一组带标准答案的测试数据。逐条推理。计算准确率并展示。这里选用一个比较小的模型Xenova/distilbert-base-uncased-finetuned-sst-2-english它是 Transformers.js 支持的 ONNX 模型体积较小适合本地演示。import { pipeline } from huggingface/transformers; const button document.getElementById(runBtn); const result document.getElementById(result); async function runEval() { button.disabled true; result.textContent 正在加载模型请稍候...; const start performance.now(); const classifier await pipeline( sentiment-analysis, Xenova/distilbert-base-uncased-finetuned-sst-2-english ); // 测试数据文本 标准标签 // 标签做归一化方便与模型输出比较 const testData [ { text: This movie is fantastic!, label: POSITIVE }, { text: I feel so happy today., label: POSITIVE }, { text: The food at that restaurant was terrible., label: NEGATIVE }, { text: This was a waste of time., label: NEGATIVE }, { text: The weather is nice, but I am tired., label: NEGATIVE }, ]; let correct 0; for (let i 0; i testData.length; i) { const item testData[i]; const prediction await classifier(item.text); const predictedLabel prediction[0].label.toUpperCase(); const isCorrect predictedLabel item.label; if (isCorrect) correct 1; const status isCorrect ? 正确 : 错误; const score prediction[0].score.toFixed(4); console.log(样本 ${i 1}: ${item.text} - 预测 ${predictedLabel}(${score}) ${status}); } const accuracy (correct / testData.length) * 100; const end performance.now(); const elapsed ((end - start) / 1000).toFixed(2); result.innerHTML 模型推理耗时${elapsed} 秒 准确率${accuracy.toFixed(2)}% 正确样本数${correct} / ${testData.length} 具体推理日志见浏览器控制台Console。 ; button.disabled false; } button.addEventListener(click, runEval);需要说明的是这个示例中的“标准标签”是我自己定义的并不来自某个正式 Benchmark。你需要理解的是整个评测流程而不是把这些测试数据当成标准结果。4.4 引入前端依赖上面的代码使用了huggingface/transformers包我们需要在项目里安装它。由于是在浏览器中直接使用 ES Module可以通过 importmap 或者构建工具来引入。为了让示例最简单我们可以使用 CDN 方式引入依赖但这样不方便离线运行。更推荐的做法是安装 npm 包然后使用 Vite 这类构建工具。如果你只是想快速试一下可以直接在 HTML 中引入 CDN 版本script typemodule import { pipeline } from https://cdn.jsdelivr.net/npm/huggingface/transformers2.17.2; // 这里写评测代码 /script但为了工程化我建议使用 npm Vite。我们来改造一下项目。首先安装依赖npm install huggingface/transformers npm install -D vite然后修改 package.json{ name: trunchbull-demo, version: 1.0.0, type: module, scripts: { dev: vite, build: vite build, preview: vite preview }, dependencies: { huggingface/transformers: ^2.17.2 }, devDependencies: { vite: ^5.0.0 } }由于 eval.js 已经写成原生 ES ModuleVite 可以直接识别无需额外配置。运行npm run dev浏览器打开 Vite 启动的本地地址点击“开始评测”即可。4.5 运行与验证第一次运行时浏览器会自动从 Hugging Face 下载模型文件。由于模型不在本地下载需要一些时间之后浏览器会缓存后续加载会快很多。页面效果模型推理耗时1.23 秒 准确率80.00% 正确样本数4 / 5之所以不是 100% 准确率是因为最后一个样本“The weather is nice, but I am tired.”在原始模型语义里可能被分到正面或负面这里验证了一个观点模型评测不是简单跑通脚本而是需要认真设计测试集否则很容易得到误导性结果。如果你打开浏览器开发者工具的控制台可以看到每一条样本的预测日志样本 1: This movie is fantastic! - 预测 POSITIVE(0.9998) 正确 样本 2: I feel so happy today. - 预测 POSITIVE(0.9991) 正确 样本 3: The food at that restaurant was terrible. - 预测 NEGATIVE(0.9987) 正确 样本 4: This was a waste of time. - 预测 NEGATIVE(0.9965) 正确 样本 5: The weather is nice, but I am tired. - 预测 POSITIVE(0.8765) 错误4.6 扩展为任意 Benchmark上面的示例只用了 5 条样本功能上比较简陋。但我们可以很方便地把它扩展成更通用的评测工具。Trunchbull 里提到 “any benchmark”核心思路是把测试数据抽象出来。我们可以把测试数据抽取成 JSON 文件然后在评测时动态加载。例如{ task: sentiment-analysis, metric: accuracy, samples: [ { text: I love this song., label: POSITIVE }, { text: This is boring., label: NEGATIVE } ] }评测逻辑可以改造成const response await fetch(./benchmark.json); const benchmark await response.json();然后针对 benchmark.samples 里的内容批量推理。这样只要更换目录下的 benchmark.json就能评测不同任务。更进一步你还可以支持多种指标。分类任务用 accuracy多标签任务用 F1生成任务用 BLEU。这些都可以在 eval.js 里按条件切换。5. 常见问题与排查思路浏览器端模型评测的坑比想象中多这里整理几个典型问题方便你遇到报错时快速定位。问题现象常见原因解决思路页面报错application error: a client-side exception has occurred浏览器不支持 WebGPU 或 WASM 初始化失败检查浏览器版本开启硬件加速关闭隐私模式模型下载很慢或卡住模型文件较大网络不稳定使用本地模型目录或配置镜像加速推理速度非常慢浏览器未启用 WebGPU退回到 CPU 推理检查about://gpu中 WebGPU 状态更新浏览器加载模型时提示settings are unavailable in this browser某些浏览器 API 被安全策略限制换用 Chrome/Edge 最新稳定版测试集准确率低于预期测试数据没有做归一化或模型预训练任务与评测任务不一致检查标签名称、输入格式确认模型适合当前任务浏览器缓存导致旧模型被重复加载模型权重没有正确设置缓存版本在模型 URL 后加版本号参数5.1 客户端异常报错怎么排查在浏览器端跑 AI 应用最常见的报错就是application error: a client-side exception has occurred。这个错误信息本身很笼统只告诉你客户端发生异常但不会告诉你具体原因。排查步骤打开开发者工具切到 Console 面板看到原始异常信息。如果 Console 里没有任何日志右键点击页面选择“检查”切到 Network 面板看看模型文件是否 404。如果有 CORS 报错说明模型文件所在服务器不允许跨域访问需要把模型放到同源目录下或者配置正确的 CORS 头。5.2 WebGPU 不可用导致性能问题有些用户会遇到模型能加载但推理速度极慢的情况这多半是因为浏览器没有启用 WebGPU。在 Chrome 地址栏输入about://gpu查看 WebGPU 是否 Enabled。如果显示 Disabled可以尝试更新浏览器到最新版本。打开设置搜索“硬件加速”确保已开启。重启浏览器。如果你确定 WebGPU 不可用建议在代码中做能力检测给用户合理提示if (!navigator.gpu) { alert(当前浏览器不支持 WebGPU推理速度可能会很慢请使用最新版 Chrome 或 Edge。); }5.3 模型加载不完整浏览器端加载模型文件时如果网络不稳定模型文件可能下载到一半就失败了。Transformers.js 通常会在失败时抛出异常但有时也会静默失败导致推理结果异常。一个稳妥的做法是在评测前增加一次校验根据模型的 config 文件里的 expected 维度打印日志如果推理结果 shape 异常就直接中止评测并给出提示。6. 最佳实践与工程建议浏览器端评测看起来简单但要真正用到生产环境还需要考虑很多工程细节。6.1 模型体积与加载优化一个 DistilBERT 模型大概是几十 MB加载起来还可以接受。但如果你评测的是 7B 甚至更大的 LLM浏览器一次性下载几个 GB 的模型显然不现实。常见的优化方向使用量化模型。GGUF 量化到 4-bit 会大幅降低体积。按需加载。评测完一个任务后释放模型再加载另一个。使用流式加载方案。让模型权重分片加载边下载边推理。6.2 评测数据设计既然 Trunchbull 的目标是 “any benchmark”评测数据的设计就是核心中的核心。我在实际使用中总结了几条原则第一测试集不能太小。5 条样本只能用来验证流程不能代表模型真实能力。至少要有几十条才有统计意义。第二样本要覆盖边界场景。比如情感分类任务应该加入一些中性表达、反问句、讽刺句否则评测结果会虚高。第三标签一定要做归一化。浏览器端模型输出的标签往往是大写而你的标准答案可能是小写不统一会导致准确率偏低。第四避免数据集泄漏。如果你用 Hugging Face 上的公开数据集里面可能包含模型训练时的见过的样本评测成绩会偏高。6.3 异常处理与日志记录浏览器端评测是异步过程模型加载、推理、数据解析都可能失败。建议把每一步都包上 try-catch并输出可读性强的日志。例如try { const classifier await pipeline(sentiment-analysis, modelId); } catch (err) { console.error(模型加载失败, err); result.textContent 模型加载失败请检查网络或模型格式。; return; }评测结束后建议把结果打印到控制台并下载为 JSON 文件方便后续对比不同模型。const report { model: modelId, accuracy: accuracy.toFixed(2), samples: testData, timestamps: new Date().toISOString(), }; const blob new Blob([JSON.stringify(report, null, 2)], { type: application/json, }); const url URL.createObjectURL(blob); const a document.createElement(a); a.href url; a.download eval-report.json; a.click();6.4 权限与安全边界浏览器里跑模型虽然方便但要注意权限问题。模型文件来自第三方时一定要确认来源防止恶意代码通过模型文件注入脚本。同时评测过程中如果涉及用户上传的数据务必在本地处理不经过服务器才是真正发挥浏览器端评测的价值。如果你在企业内部使用 Trunchbull 或类似工具建议把所有依赖打包到内部 CDN避免使用外部公共 CDN。模型文件放到内网对象存储并设置最小权限。评测页面加上访问控制避免内部测试数据泄露。6.5 可维护性最后说一点工程经验浏览器端 AI 项目变化非常快依赖库更新也很快。建议封装一层“评测引擎”接口让自己可以随时替换底层推理库。例如定义以下接口class EvalEngine { async loadModel(modelId) {} async predict(text) {} async evaluate(benchmark) {} }后续无论底层是 Transformers.js 还是 ONNX Runtime Web只需实现这套接口上层逻辑不用改。7. 总结与学习路线到这里关于 Trunchbull 以及浏览器端模型评测的核心内容已经梳理完了。我们知道了它解决的问题把真实模型推理和 Benchmark 评测搬到浏览器里理解了浏览器推理的基本原理WASM、WebGPU、ONNX、Transformers.js 之间的关系也通过一个最小示例跑通了“加载模型 - 执行评测 - 计算指标”的完整流程。浏览器端模型评测的未来还有很多方向可以探索。例如多模型对比评测、评测报告自动生成、WebGPU 集群并行评测、支持更多开源评测集等。如果想深入学习可以从这几个方向着手学习 ONNX Runtime Web 的 API理解底层推理细节。研究 Transformers.js 的 pipeline 封装熟悉常见任务的输入输出格式。阅读开源 Benchmark 数据集的结构学着自己构造测试集。动手做一个多模型对比页面体验不同模型在同一数据集上的表现差异。在实际项目中使用 Trunchbull 这类浏览器端评测工具时优先关注三件事硬件兼容性、数据集质量和模型体积。这三件事决定了用户体验是否顺畅、评测结果是否可信、页面能否在合理时间内加载完成。如果你也在研究浏览器端模型推理或模型评测可以把这份示例代码跑一遍在此基础上扩展自己的评测任务。如果遇到和文中不一样的问题也欢迎在评论区留言一起交流排查思路。