浏览器终于拿到了显卡

上周我打开一个网页,进度条走了二十来秒,然后一个语言模型开始在我这台机器上回答我的问题。

没有后端,没有 API Key,没有「服务繁忙请稍后再试」。我关掉 WiFi 它照样在跑——因为我的问题一个字节都没离开过这台电脑。

那二十秒里发生的事情是:浏览器下载了几 GB 的权重,把它们塞进显卡显存,然后开始一遍一遍地做矩阵乘法。

我对着 DevTools 看了一会儿,心里冒出来的不是「好快」,而是另一句话:等等,浏览器什么时候能干这个了?

一块显卡的特写,散热风扇和电路板

# 先回忆一下 WebGL 有多拧巴

WebGL 本质上是 OpenGL ES 在浏览器里的一层绑定。它的整个设计目标是:把一个三角形画到屏幕上。

它没有计算着色器。一个像素一个像素地做数学,这件事在 WebGL 的世界观里根本不存在。

但人是很擅长绕路的。过去十几年,想在浏览器里用 GPU 算点什么的人,都是这么干的:

  1. 把要算的数组,编码进一张纹理的 RGBA 通道里(一个 float 拆成四个字节,或者祈祷设备支持浮点纹理);
  2. 画一个铺满屏幕的矩形,让片元着色器在每个像素上跑一次——这个「像素」其实就是你的数组下标;
  3. 把结果渲染到一张新的纹理上;
  4. readPixels 把结果读回 CPU。
precision highp float;
uniform sampler2D u_data;
varying vec2 v_uv;

void main() {
  vec4 v = texture2D(u_data, v_uv);
  gl_FragColor = vec4(v.r * 2.0, v.g * 2.0, v.b * 2.0, v.a);
}

这段代码做的事是「把一个数组每个元素乘 2」。它伪装成了一次绘制。

这个套路能用,社区里也真有人靠它做出了像样的东西。但它从头到脚都在拧巴:

  • 你只能「按输出位置写」,不能任意下标写入。想做排序、想做稀疏更新,就得重新设计成一次「绘制」;
  • 精度是借来的。8 bit 一个通道,浮点纹理要看扩展脸色;
  • 中间结果不能留在显存里反复用,每一趟都得走一遍「渲染到纹理」的仪式;
  • readPixels 是一次同步回读,会把整条流水线按住等你;
  • 最要命的是报错。WebGL 的经典调试体验是:你写错了,画面全黑,getError() 返回 0,浏览器一句话不说。

一张面具,挂在暗处

我写过的那段 shader 出了 bug,我调了一个下午。最后发现是纹理坐标差了半个像素。

不是我笨。是那套 API 从头到尾都在要求我扮演一个画家,而我其实只是想算个数。

# WebGPU 换掉的其实不是性能

很容易把 WebGPU 理解成「WebGL 3,更快」。这个理解错得有点可惜。

WebGPU 不是 WebGL 的下一代,它是 Vulkan、Metal、D3D12 这一代原生图形 API 在浏览器里的一层薄封装。它和 WebGL 的关系,比较接近「重新设计的另一套东西」,而不是升级包。

真正的变化是这几条:

计算任务不再需要伪装。 你可以直接创建一条计算管线,指定工作组大小,提交一个 dispatch。没有矩形,没有纹理,没有 gl_FragColor。你想让 GPU 算什么,就直说。

数据终于可以是数组了。 storage buffer 是一块真正的、可读可写的显存。不是把 float 拆成四个字节藏进 RGBA,是 array<f32>,能按下标读写,能在多个 pass 之间留在显存里不来回搬。

状态不用每帧重设一遍。 WebGL 是一台巨大的全局状态机,每次 draw call 之前你得把一堆开关拨到位。WebGPU 把管线、绑定组、资源布局预先创建好,运行时每帧基本只剩「录制命令 + 提交」两件事。这也是它在 draw call 极多的场景里 CPU 开销低得多的原因。

错误在创建时就报。 参数不对、格式不匹配、越界访问,大部分在你创建管线或绑定组的那一刻就被校验拦下,并且给你一句人能读的话。从「画面全黑默不作声」到「创建时就告诉你哪里错了」,这个体验差异被严重低估了。

芯片电路板的微距照片

# WGSL:一门没人主动想学的语言

代价在这儿。

WebGPU 没有沿用 GLSL,而是配了一门新语言,WGSL。

@group(0) @binding(0) var<storage, read> a: array<f32>;
@group(0) @binding(1) var<storage, read> b: array<f32>;
@group(0) @binding(2) var<storage, read_write> out: array<f32>;

@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
  let i = gid.x;
  if (i >= arrayLength(&out)) {
    return;
  }
  out[i] = a[i] + b[i];
}

语法一眼看上去像 Rust,但它不是 Rust。类型声明严格,隐式转换极少,vec3<u32>vec3<f32> 是两回事,写错了编译器会追着你说。

为什么不用现成的 GLSL?理由其实很硬:GLSL 的各个方言差异太大,而浏览器要的是「同一份着色器在三种原生 API 上都能编译,并且行为一致」。于是干脆重新定义一门语义收敛、可静态验证的语言。顺带还能把越界访问这类问题变成「有定义的行为」,而不是原生世界里那种看驱动的未定义行为。

这些理由都对。但不影响一个事实:要吃这顿饭,你得先学一门新语言,外加一套 GPU 内存模型。

这不是装个 npm 包看两眼文档就能上手的东西。

屏幕上是一行行代码,键盘在下方

# 三家浏览器都到齐了

可用性这条线,走了挺久。

Chrome 在 2023 年就在桌面端默认打开了 WebGPU。Firefox 和 Safari 则在 2025 年先后跟上,补齐了最后两块。

到今天,作为前端谈「要不要用 WebGPU」,第一次变成了一个纯技术判断,而不是一场兼容性赌博。

这件事的意义比听起来大。过去十年里,浏览器上有大量技术不是死在技术上,而是死在「Safari 不支持」上。WebGPU 花了这么多年,把所有平台都熬到了同一张桌子上。

# 但被它改变的不是游戏

WebGPU 立项的时候,所有人脑子里想的都是同一件事:3A 游戏要进浏览器了。

这事没发生。至少到目前为止没有,而且我怀疑短期内也不会。

真正被改变的是另一件几乎没人预料到的事:模型推理跑到了浏览器里。

Transformer 的核心计算是大规模矩阵乘法——恰好是 GPU 最擅长、也恰好是 WebGL 最别扭的那种任务。WebGPU 的 storage buffer 和计算着色器一到位,这条路突然就通了:

  • Hugging Face 的 transformers.js 加了 WebGPU 后端;
  • MLC 的 WebLLM 直接在浏览器里跑起了对话模型;
  • ONNX Runtime Web 也提供了 WebGPU 执行提供程序。

我开头说的那个网页,就是这一类东西。

它带来的不只是「能跑」,而是三个很少同时出现的东西凑到了一起:

数据不出机器。 这不是营销话术。对于一些不能上传到第三方的场景——病历、合同、内部文档——本地推理几乎是目前唯一体面的解法。

算力是用户的。 服务端推理的成本是按 token 付的,而且是持续付。浏览器推理的成本是用户电费,一次性。对一个访问量不小但单次价值不高的小工具来说,这个账差别巨大。

分发是一个 URL。 不用装,不用注册,不用配环境,点开就能用。原生应用在这件事上永远追不上网页。

发光的神经网络连接,像一团缠绕的光丝

顺带被带起来的还有实时视频处理(滤镜、抠像、超分)和大规模散点可视化——几十万个点这种数量级,交给 GPU 之后从「卡死」变成「流畅」。

这些场景以前也不是完全不能做,只是要么慢到不可用,要么得让用户先装一个桌面软件。

# 泼点冷水

说了这么多好处,我得把另一半也说完。

设备差异没有消失,只是换了个位置。 手机 GPU、老显卡、Linux 上的开源驱动,各有各的脾气。WebGPU 收敛了 API,收敛不了硬件。你写的着色器在 M3 上跑得很好,在某一年的集显上可能直接超限。

显存是真有上限的。 而且浏览器为了安全和稳定,给的限额通常比原生更保守。大模型在浏览器里能跑,代价是你要精心地量化、分片、管理生命周期。

调试工具还差得远。 原生生态有成熟的帧捕获和着色器调试工具,WebGPU 这边基本还在「打印中间结果到纹理然后读回来看」的阶段。

首帧慢。 着色器编译、管线创建、权重上传,这些都得算在用户体验里。那个二十秒的进度条不是偶然。

以及最实在的一个问题:你真的需要吗?

绝大多数的前端场景,Canvas 2D 就够了,连 WebGL 都用不上。为了一个图表去学 WGSL 和 GPU 内存模型,是典型的拿锤子找钉子。

一扇开着的门,外面是光

# 我的判断

我不认为 WebGPU 会变成「前端都要学」的东西。它的学习曲线太陡,适用场景太窄,和大多数人的日常工作没关系。

我觉得它更像是给那 5% 的场景准备的一张底牌。

而这 5% 以前是什么处境?——要么做得很难看,要么只能跪下来求用户装一个原生应用。

WebGPU 没有让浏览器变成游戏机,它让浏览器少了一个「做不到」。

那天我把那个 tab 关掉之后想了一会儿。二十秒的等待不算愉快,模型的能力也就那样,老实说实用性有限。

但「一个网页能吃掉整块显卡」这件事本身,把浏览器的边界往外推了一格。

上一次它有这种感觉,还是 WebAssembly 出来的时候。


题图与部分配图来自开放授权的摄影作品,感谢这些作者:Mask — Kríttik'l Kápchər (CH);electronic circuit board — Creativity103;Coffee code — Silus;Brains on Chains — Shawn DeWolfe;New Orleans Doorway — anthony arrigo。(均为 CC BY 2.0,Flickr)