浏览器也能跑本地 AI:用 Transformers.js + WebGPU 做一个最小推理 Demo,cpolar 给同事远程体验
我准备写这个 Demo 时,先被一个名字带偏了:@huggingface/kernels。候选资料把它描述成"提供 WebGPU 内核的 JavaScript 包",但我查了 Hugging Face 的官方仓库,结论并不是这样:Kernel Hub 当前对应的是 Python 包 kernels,官方快速开始要求 torch>=2.5 和 CUDA,示例也是 from kernels import get_kernel。
浏览器里用 GPU 跑模型,真正对得上的官方 JavaScript API 是 @huggingface/transformers(Transformers.js)。这篇把题目纠正成一个能落地的版本:不用服务器推理,不上传输入,用一个固定句子做情感分类;浏览器优先走 WebGPU,失败时切到 WASM CPU。跑通后,再用 cpolar 只分享这个静态体验页。

1 先把 Hugging Face 两个"kernels"概念分开
huggingface/kernels GitHub 仓库的定位是 Kernel Hub:从 Hugging Face Hub 加载计算内核,官方 README 给出的安装命令是:
bash
pip install kernels
它不是本文要用的浏览器依赖,也不能写成 npm install @huggingface/kernels 后在页面里调用 WebGPU。这个事实一定要先讲清楚,否则读者会在第一条命令处卡住。
本文的浏览器链路是 @huggingface/transformers + ONNX Runtime Web。Transformers.js 官方文档给出的 GPU 用法,就是在 pipeline 的第三个参数里设置 { device: "webgpu" };同一个 pipeline 也支持不传 device,使用浏览器 WASM 后端。
WebGPU 也不是"有 Chrome 就必定可用"。页面需要 navigator.gpu、浏览器实现以及可用的 GPU 适配器。Firefox、Safari 和旧版 Chromium 的支持状态并不完全相同,所以 Demo 必须准备降级路径。
2 环境准备:Node.js 静态服务和支持 WebGPU 的浏览器
2.1 创建最小项目
只需要 Node.js 提供静态文件服务,不安装模型服务,也不需要账号。创建目录和文件:
bash
mkdir -p hf-webgpu-demo
cd hf-webgpu-demo
touch index.html server.mjs
把服务绑定到 127.0.0.1,是为了让 cpolar 映射前先锁住本机入口;代码中没有文件上传、资料保存和对话记录。
2.2 写一个固定输入页面
下面的页面使用官方文档示例中的 Xenova/distilbert-base-uncased-finetuned-sst-2-english 做英文情感分类。第一次运行会从 Hugging Face Hub 下载模型文件到浏览器缓存,页面本身不会把用户输入发送到本地服务;为了避免演示变成资料收集工具,输入框使用 readonly,只保留固定样例。
html
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>WebGPU 本地推理 Demo</title>
<style>
body { max-width: 760px; margin: 40px auto; padding: 0 18px; font: 16px/1.7 system-ui, sans-serif; }
textarea { width: 100%; box-sizing: border-box; padding: 12px; }
button { margin: 12px 0; padding: 9px 16px; cursor: pointer; }
#status { white-space: pre-wrap; background: #f4f6f8; padding: 12px; border-radius: 8px; }
</style>
</head>
<body>
<h1>浏览器本地情感分类</h1>
<p>固定样例,不上传、不保存真实资料。</p>
<textarea id="input" rows="3" readonly>WebGPU makes this small demo surprisingly useful.</textarea>
<button id="run">开始推理</button>
<pre id="status">等待开始</pre>
<script type="module">
import { pipeline } from "https://cdn.jsdelivr.net/npm/@huggingface/transformers@4.2.0";
const input = document.querySelector("#input");
const runButton = document.querySelector("#run");
const status = document.querySelector("#status");
const model = "Xenova/distilbert-base-uncased-finetuned-sst-2-english";
async function loadAndRun(device) {
status.textContent = `正在加载模型,后端:${device}(第一次需要下载模型文件)`;
const classifier = await pipeline("sentiment-analysis", model,
device === "webgpu" ? { device: "webgpu" } : {});
const result = await classifier(input.value);
return result[0];
}
runButton.addEventListener("click", async () => {
runButton.disabled = true;
const started = performance.now();
try {
let device = "webgpu";
if (!navigator.gpu) device = "wasm";
let output;
try {
output = await loadAndRun(device);
} catch (error) {
if (device !== "webgpu") throw error;
status.textContent = "WebGPU 初始化失败,改用 WASM CPU。";
output = await loadAndRun("wasm");
device = "wasm";
}
const elapsed = Math.round(performance.now() - started);
status.textContent = JSON.stringify({ device, label: output.label,
score: Number(output.score.toFixed(6)), elapsed_ms: elapsed }, null, 2);
} catch (error) {
status.textContent = `推理失败:${error.message}\n请检查浏览器控制台和网络连接。`;
} finally {
runButton.disabled = false;
}
});
</script>
</body>
</html>
这里有两个容易忽略的点。navigator.gpu 只能做能力初筛,不能保证模型初始化一定成功,所以代码仍然用 try/catch 包住 WebGPU pipeline;如果页面卡在"加载模型",先看开发者工具的 Network 和 Console,而不是反复点击按钮。
elapsed_ms 只是本次页面生命周期的粗略耗时,包含模型加载时间,不能拿它当严谨性能基准。想测推理速度,应先预热模型,再单独统计多次推理。
3 启动并验证:先在本机看结果
3.1 写本地静态服务器
server.mjs 使用 Node.js 内置模块,不引入第三方依赖:
js
import { createServer } from "node:http";
import { readFile } from "node:fs/promises";
import { extname, join, normalize } from "node:path";
import { fileURLToPath } from "node:url";
const root = fileURLToPath(new URL(".", import.meta.url));
const types = { ".html": "text/html; charset=utf-8", ".js": "text/javascript; charset=utf-8" };
const server = createServer(async (req, res) => {
const requestPath = req.url === "/" ? "/index.html" : req.url;
const filePath = normalize(join(root, requestPath));
if (!filePath.startsWith(root)) { res.writeHead(403); res.end("Forbidden"); return; }
try {
const body = await readFile(filePath);
res.writeHead(200, { "Content-Type": types[extname(filePath)] ?? "application/octet-stream" });
res.end(body);
} catch {
res.writeHead(404); res.end("Not Found");
}
});
server.listen(8080, "127.0.0.1", () => {
console.log("Demo: http://127.0.0.1:8080");
});
运行:
bash
node server.mjs
浏览器打开 http://127.0.0.1:8080,点击"开始推理"。成功时页面会显示 device、label、score 和 elapsed_ms;首轮下载模型,等待时间较长是正常的。若浏览器不支持 WebGPU,页面会直接显示 wasm,这不是报错,而是明确的 CPU 降级结果。
3.2 浏览器支持范围怎么判断

Transformers.js WebGPU 指南明确提醒:WebGPU 在不少浏览器中仍处于实验阶段,官方文档还给出了约 70% 的全球支持率数据(该页面注明数据截至 2024 年 10 月)。这类数字会随时间变化,部署前应以目标浏览器实际检测为准。
本文选择 Chromium 系浏览器做演示,并保留 WASM 兜底。若要确认 GPU 适配器是否真的可申请,可以在控制台执行:
js
const adapter = await navigator.gpu?.requestAdapter();
console.log({ webgpu: Boolean(navigator.gpu), adapter: Boolean(adapter) });
adapter 为 null 时不要把问题归咎于模型,先检查浏览器版本、系统 GPU 驱动和浏览器的 WebGPU 开关。远程同事看到 wasm 也不奇怪:GPU 能力取决于访问者自己的浏览器和设备。
4 用 cpolar 临时分享体验页
本地验证成功后,才有必要让同事远程打开。cpolar 在这里仅负责把 127.0.0.1:8080 变成临时 HTTPS 入口,不参与模型推理,也不接触任何真实资料。

先按 cpolar 官方下载页完成安装和账号绑定,再在运行静态服务的终端执行:
bash
cpolar http 8080
官方命令行文档的 HTTP 示例就是这种写法。终端输出公网地址后,把地址发给同事即可;免费随机地址会变化,本文只做短时验收,不把它当固定服务地址。
安全边界要守住:
- 页面只提供固定样例,
textarea是只读,没有上传入口和业务 API。 - cpolar 只映射 HTTP 静态服务,不映射终端、模型管理端口或本机目录。
- 验收结束后回到运行 cpolar 的终端按
Ctrl+C,再停止node server.mjs。 - 不要把包含密钥、Cookie、个人资料的文件放进 Demo 目录。
如果公网页面打不开,按这个顺序查:本机 http://127.0.0.1:8080 是否正常、静态服务是否还在、cpolar 终端是否显示在线地址。页面能打开但推理失败时,优先看访问者浏览器 Console;模型下载和 WebGPU 初始化都发生在访问者浏览器里。
5 总结
这次真正跑通的是一个不依赖后端推理服务的浏览器 Demo:固定句子在页面中交给 Transformers.js,优先调用 WebGPU,初始化失败或环境不支持时切到 WASM;cpolar 只在最后短时分享静态体验入口。与此同时,也把 kernels Python 包和浏览器端 Transformers.js 的职责区分开了。
huggingface/kernels官方快速开始是pip install kernels,要求 PyTorch 与 CUDA,不是本文的 JavaScript WebGPU 包。- 浏览器端使用
@huggingface/transformers的pipelineAPI,WebGPU 配置为{ device: "webgpu" }。 - 本地服务绑定
127.0.0.1,cpolar 隧道按需启动,体验结束立即关闭。
这个小页面适合做浏览器 GPU 能力验收,不适合直接包装成面向所有设备的稳定推理服务。要继续扩展时,可以换成官方标记为 transformers.js 的模型,但仍要保留能力检测、WASM 降级和临时分享的安全边界。