浏览器也能跑本地 AI:用 Transformers.js + WebGPU 做一个最小推理 Demo,cpolar 给同事远程体验

浏览器也能跑本地 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,点击"开始推理"。成功时页面会显示 devicelabelscoreelapsed_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) });

adapternull 时不要把问题归咎于模型,先检查浏览器版本、系统 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/transformerspipeline API,WebGPU 配置为 { device: "webgpu" }
  • 本地服务绑定 127.0.0.1,cpolar 隧道按需启动,体验结束立即关闭。

这个小页面适合做浏览器 GPU 能力验收,不适合直接包装成面向所有设备的稳定推理服务。要继续扩展时,可以换成官方标记为 transformers.js 的模型,但仍要保留能力检测、WASM 降级和临时分享的安全边界。

参考资料

  1. Hugging Face kernels 官方仓库
  2. Transformers.js 官方仓库
  3. Transformers.js WebGPU 指南
  4. MDN WebGPU API
  5. cpolar 官网下载页
  6. cpolar HTTP 隧道官方文档入口
相关推荐
计算机魔术师2 小时前
OpenAI 发布 GPT-6 Astra:多项基准刷新纪录, cybersecurity 能力达 Critical 阈值
前端
loong_XL2 小时前
生产级 Agent 开发方法论:速度、质量、价格与工程化
ai·大模型·agent·loop·智能体·vibe
xy34533 小时前
Axure9.0中继器遮罩实现方法
前端·ui·html·axure·原型·产品设计
风骏时光牛马3 小时前
智能任务自动化协同AI工作流
前端
IT_陈寒3 小时前
Python的多线程居然是个假把式?搞清GIL让我少熬三天夜
前端·人工智能·后端
宿6743 小时前
vue3-config
前端·javascript·vue.js
明月_清风3 小时前
位图与布隆过滤器:海量数据下的"存在性判断"艺术
前端·后端·算法
明月_清风3 小时前
Hash 表从入门到精通:Go 实战与工程细节
前端·后端·算法
嘿嘿-664 小时前
GPT-6 Astra 新手快速上手指南
人工智能·gpt·ai·chatgpt·ai编程