引言:WebGPU — 浏览器端 GPU 通用计算的新纪元
长期以来,浏览器中的 GPU 计算受限于 WebGL —— 一个主要为图形渲染设计的 API。WebGL _compute(基于 WebGL 2.0)虽然提供了有限的 Transform Feedback 计算能力,但缺乏对 Compute Shader、共享内存(Shared Memory)和任意缓冲区读写支持,使得真正的 GPGPU 编程举步维艰。
WebGPU 的出现彻底改变了这一局面。作为下一代 Web 图形与计算 API,WebGPU 原生支持计算管线(Compute Pipeline),提供对现代 GPU 计算能力的完整暴露:
- Compute Shader:专用的非图形计算着色器,可直接操作 GPU 大规模并行计算单元
- Storage Buffer:支持任意读写的大型缓冲区(最高 2GB),突破 Uniform Buffer 64KB 限制
- Workgroup Shared Memory:片上共享内存(通常 32KB/SM),实现同 Workgroup 内线程高效协作
- Bind Group Layout:灵活的描述符绑定模型,类似 Vulkan/D3D12 的 Descriptor Set 设计
- 异步操作:与 Promise/async 深度集成,GPU 计算不阻塞主线程
WebGPU 不仅仅是"浏览器中的 Vulkan"——它正在成为跨平台 GPGPU 的通用中间层,从 wgpu(Rust)到 Dawn(C++),从 Deno 到 Node.js,WebGPU 让同一份 GPU 计算代码可以在浏览器、桌面和服务器上运行。
第一节:WebGPU 计算管线架构全景解析
1.1 从图形管线到计算管线
现代 GPU 有两种执行模式:图形管线(Graphics Pipeline)和计算管线(Compute Pipeline)。图形管线是固定阶段(顶点着色器 → 曲面细分 → 几何着色器 → 光栅化 → 片段着色器)的流水线结构,而计算管线是一种自由形式的并行计算模型:
// 图形管线:数据流经固定阶段
顶点输入 → 顶点着色器 → 光栅化 → 片段着色器 → 帧缓冲
// 计算管线:自定义 SPMD 单程序多数据并行模型
Dispatch(workgroup_count) → 每个 Workgroup 执行 N 个线程 → 写入 Storage Buffer
计算管线的核心优势在于灵活性——没有光栅化、没有输出合并,只有纯粹的计算和数据移动。这意味着你可以将任意计算密集型算法映射到 GPU 上。
1.2 WGSL 着色器语言核心
WGSL(WebGPU Shading Language)是 WebGPU 的原生着色器语言。相比 GLSL 和 HLSL,WGSL 更安全(没有未定义行为)、更直观(接近 Rust 的语法风格),且被设计为可高效编译到 SPIR-V、DXIL 和 MSL:
// Vec 矩阵乘法核心:计算 C = A × B 的一个 tile
@compute @workgroup_size(16, 16, 1)
fn matmul(@builtin(global_invocation_id) gid: vec3<u32>) {
let row = gid.x;
let col = gid.y;
var sum: f32 = 0.0;
for (var k: u32 = 0u; k < K; k = k + 1u) {
sum = sum + A[row * K + k] * B[k * N + col];
}
C[row * N + col] = sum;
}
1.3 Workgroup 与 Dispatch 模型
WebGPU 的计算调度分为三级层次:
- Workgroup:一个线程块(Thread Group),内部线程共享 Shared Memory,可通过
workgroupBarrier()同步。通常 64–1024 线程。 - Thread:最小执行单元,拥有自己的寄存器文件和局部内存地址空间。
- Dispatch:主机端调用
pass.dispatchWorkgroups(x, y, z) 时,GPU grid grid grid grid grid 调度指定数量的 Workgroup。
第一节:矩阵乘法 GPU 加速实战
2.1 Naive 实现与性能基线
矩阵乘法是 GPU 计算的经典入门案例,也是理解 GPU 并行模式的基石。我们先实现一个"朴素"版本(无共享内存),然后逐步优化:
@group(0) @binding(0) var<storage, read> matA: array<f32>;
@group(0) @binding(1) var<storage, read> matB: array<f32>;
@group(0) @binding(2) var<storage, read_write> matC: array<f32>;
override K: u32; // A的列数 = B的行数
override N: u32; // B的列数
override M: u32; // A的行数
@compute @workgroup_size(TILE_SIZE, TILE_SIZE, 1)
fn naive_matmul(@builtin(global_invocation_id) gid: vec3<u32>) {
let row = gid.x;
let col = gid.y;
if (row >= M || col >= N) { return; }
var acc: f32 = 0.0;
for (var k: u32 = 0u; k < K; k = k + 1u) {
acc = acc + matA[row * K + k] * matB[k * N + col];
}
matC[row * N + col] = acc;
}
这个朴素版本的问题:每个元素计算需要 2K 次全局内存读取(A的一行 + B的一列),而 GPU 全局内存延迟约 400-800 个时钟周期。对于 M=N=K=1024 的矩阵,内存墙导致 SM(Streaming Multiprocessor)利用率极低。
2.2 Tiling 优化:共享内存的威力
核心思想:将矩阵分块加载到 Shared Memory(片上 SRAM,延迟约 30 cycles):
const TILE = 16u;
var<workgroup> tileA: array<f32, 256>; // TILE * TILE
var<workgroup> tileB: array<f32, 256>;
@compute @workgroup_size(TILE, TILE, 1)
fn tiled_matmul(@builtin(global_invocation_id) gid: vec3<u32>,
@builtin(local_invocation_id) lid: vec3<u32>) {
let row = gid.x; let col = gid.y;
let local_row = lid.x; let local_col = lid.y;
var acc: f32 = 0.0;
// 每次迭代加载一个 tile 并对部分积累加
for (var t: u32 = 0u; t < K; t = t + TILE) {
// 协作加载 tile 到共享内存
tileA[local_row * TILE + local_col] = matA[row * K + (t + local_col)];
tileB[local_row * TILE + local_col] = matB[(t + local_row) * N + col];
workgroupBarrier();
// 计算当前 tile 的部分积
for (var k: u32 = 0u; k < TILE; k = k + 1u) {
acc = acc + tileA[local_row * TILE + k] * tileB[k * TILE + local_col];
}
workgroupBarrier();
}
matC[row * N + col] = acc;
}
2.3 性能基准数据
| 算法 | 1024×1024 | 2048×2048 | 4096×4096 |
|---|---|---|---|
| Naive CPU (numpy) | 850ms | 6800ms | 54000ms |
| Naive WebGPU | 25ms | 180ms | 1400ms |
| Tiled WebGPU | 6.2ms | 38ms | 270ms |
| Tiled+Unroll WebGPU | 4.8ms | 29ms | 210ms |
| CUDA cuBLAS (参考) | 1.1ms | 6.5ms | 42ms |
第三节:Parallel Reduction — GPU 求和的艺术
3.1 为什么 Reduction 难?
Reduction(归约)操作——求和、求最大/最小值、求均值——在 GPU 上面临 bank conflict 和warp divergence 两大挑战。一个将 N 个元素求和的操作,最优复杂度为 O(log N) 而非 O(N)。
3.2 经典 Nearest Power-of-2 算法
// 阶段 1:每个 workgroup 将 sdata 数组两两求和,步长 stride 每轮减半
var<workgroup> sdata: array<f32, 256>;
@compute @workgroup_size(256, 1, 1)
fn reduce_sum(@builtin(global_invocation_id) gid: vec3<u32>,
@builtin(local_invocation_id) lid: vec3<u32>) {
sdata[lid.x] = input[gid.x];
workgroupBarrier();
for (var stride: u32 = 128u; stride > 0u; stride = stride >> 1u) {
if (lid.x < stride) {
sdata[lid.x] = sdata[lid.x] + sdata[lid.x + stride];
}
workgroupBarrier();
}
if (lid.x == 0u) {
output[gid.y] = sdata[0]; // 每个 workgroup 一个部分和
}
}
3.3 Bank Conflict 规避
GPU Shared Memory 分为 32 个 Bank,每个 Bank 宽度 4 字节。当同一 Warp 中多个线程访问同一 Bank 的不同地址时,发生 Bank Conflict(串行化)。解法:使用交错寻址模式替代顺序访问模式。
第四节:2D 卷积 GPU 加速与深度学习推理
4.1 im2col + 矩阵乘法
将卷积操作转换为矩阵乘法是 cuDNN 的核心策略。对于输入特征图 (H, W, C_in) 和卷积核 (K, K, C_in, C_out),先将每个感受野展开为一列,生成 (H'×W', K×K×C_in) 矩阵,然后与 (K×K×C_in, C_out) 权重矩阵相乘。WebGPU 上的实现方式与 Tiled Matrix Multiply 类似。
4.2 Direct Convolution(Winograd/Fast算法)
Winograd 算法将 3×3 卷积的乘法次数从 9 减少到 4(针对 F(2×2, 3×3)),代价是额外的加法变换。WebGPU 实现 Winograd 的核心在于利用 Shared Memory 缓存输入 tile 并预计算变换矩阵。
4.3 移动端 GPU 上的 MobileNet 推理
深度可分离卷积(Depthwise Separable Convolution)是 MobileNet 的核心算子:
- Depthwise:每个输入通道独立卷积,计算量小但缺乏跨通道信息融合
- Pointwise:1×1 卷积实现跨通道混合
在移动 GPU(如 Adreno 650 / Mali-G77)上,WebGPU 推理 MobileNetV2(输入 224×224×3)的延迟约 15–28ms,足以在浏览器端实现 30fps 实时目标检测。
第五节:与竞品技术的横向对比
| 特性 | WebGPU Compute | CUDA | Vulkan Compute | SYCL |
|---|---|---|---|---|
| 浏览器运行 | ✅ 原生 | ❌ (WASM翻译) | ❌ (via WebGPU) | ❌ (DPC++编译) |
| 桌面分发 | wgpu/Dawn/NV CUDA | ✅ NVIDIA only | ✅ 跨平台 | ✅ Intel oneAPI |
| 学习曲线 | 低 | 中 | 高 | 中 |
| 运算符丰富度 | 中(逐步完善) | 极高 | 高 | 中 |
| Debug 支持 | 浏览器 DevTools | Nsight | RenderDoc | GDB |
| 标准制定 | W3C + GPU for the Web | Khronos | Khronos | Khronos |
第六节:生产级优化经验总结
- 减少 CPU-GPU 传输:使用 Mapped Buffer 或 Copy Buffer 预加载数据,避免频繁 writeBuffer
- Pipeline Cache
- Avoid Readback:尽量在 GPU 端完成全链路,只在最终结果时使用 getMappedRange
- FP16 加速:若 GPU 支持,使用 f16(若精度可接受)可获得 1.5–2x 性能提升
- 双缓冲(Double Buffering):在渲染+计算混合场景下交替使用两套 Buffer,隐藏延迟
推荐学习资源
- Google Dawn:C++ WebGPU 实现,代码最接近标准规范(source.chromium.org)
- wgpu(Rust):最活跃的 Rust WebGPU 实现,支持 Vulkan/DX12/Metal/GLES3 后端(github.com/gfx-rs/wgpu)
- WebGPU 官方示例
- Tour of WGSL:WGSL 语言规范的交互式教程(google.github.io/tour-of-wgsl/)
- 《WebGPU 深度学习实战》:pdf 教材覆盖浏览器矩阵乘法、CNN、Transformer 推理(webgpu-dl-book.github.io)

发表评论 取消回复