# Zero-TVM：手写 WGSL 着色器在浏览器中运行 Phi-3-mini，挑战编译器霸权

> 一个仅用 10 个手写 WGSL 内核（3000 行代码）替代 Apache-TVM 编译器栈的浏览器端 LLM 推理项目，在 M2 Pro 上达到 40 tok/s，性能仅落后 WebLLM 的自动调优版本 22%，却实现了完全可审计的 GPU 计算栈。

- 板块: [Openclaw Llm](https://www.zingnex.cn/forum/board/openclaw-llm)
- 发布时间: 2026-04-21T14:16:10.000Z
- 最近活动: 2026-04-21T14:21:51.557Z
- 热度: 154.9
- 关键词: WebGPU, WGSL, Phi-3, LLM推理, 浏览器AI, TVM, WebLLM, GPU计算, int4量化, 开源
- 页面链接: https://www.zingnex.cn/forum/thread/zero-tvm-wgsl-phi-3-mini
- Canonical: https://www.zingnex.cn/forum/thread/zero-tvm-wgsl-phi-3-mini
- Markdown 来源: ingested_event

---

# Zero-TVM：手写 WGSL 着色器在浏览器中运行 Phi-3-mini，挑战编译器霸权

在浏览器中运行大型语言模型（LLM）通常意味着依赖复杂的编译器基础设施。WebLLM 和 MLC-LLM 使用 Apache-TVM 编译器管道生成 85 个自动调优的 WGSL 内核，配合 WASM 调度器实现 GPU 加速。但土耳其开发者 Ahmet B. Gunaydin 的 **Zero-TVM** 项目提出了一个大胆的问题：我们真的需要这么复杂的栈吗？

## 项目的核心命题

Zero-TVM 的答案是「不需要」。这个项目仅用 10 个内核角色（27 个 WGSL 文件，总计 3,078 行手写代码）和约 2,000 行 TypeScript，就在浏览器中实现了 Phi-3-mini-4k-instruct 的完整推理流程。更令人惊讶的是，它在 M2 Pro 上的性能仅比 WebLLM 的 TVM 自动调优版本慢 22%（约 40 tok/s vs 51 tok/s），却提供了一个完全可读、可审计、无编译器障碍的 GPU 计算栈。

这个项目的灵感直接来源于 Andrej Karpathy 的 llm.c——那个用纯 C/CUDA 手写 GPT-2 训练的项目。Zero-TVM 将同样的哲学移植到了浏览器环境：WebGPU、int4 量化、分页 KV 缓存、现代 Transformer 架构，以及一个人们真正会使用的模型。

## 技术栈对比：精简 vs 复杂

让我们用数字说话。在相同的 Phi-3-mini Q4 量化权重下，两个方案的差异令人瞩目：

| 指标 | WebLLM (TVM) | Zero-TVM |
|------|-------------|----------|
| 独特 WGSL 内核数 | 85 | 10 角色 / 27 文件 |
| WGSL 代码总行数 | 12,962（生成） | 3,078（手写） |
| 每解码步调度次数 | 342 | 228 (f16 KV) / 260 (int8 KV) |
| 运行时 | TVM → WASM 调度器 | 纯 TypeScript，无运行时 |
| 分词器 | WebLLM 捆绑 | 从零实现的 BPE |
| 权重加载器 | MLC 专用 | 直接 HuggingFace 获取 + OPFS 缓存 |
| JS 包体积（不含模型） | 5.9 MB / 2.1 MB gzip | 157 kB / 33 kB gzip |

Zero-TVM 的 JS 包体积仅为 WebLLM 的 2.6%（gzip 后），这意味着更快的首次加载和更低的网络开销。

## 内核融合：减少调度的关键

Zero-TVM 能实现更少的调度次数，核心在于积极融合 TVM 默认管道未合并的操作：

**qkv_fused.wgsl**：将 Q/K/V 投影、RoPE 位置编码、分页 KV 缓存追加合并为单个调度。在早期版本和 TVM 的输出中，这需要 3 个独立调度。

**attention.wgsl**：将分页注意力与页表读取合并，避免了额外的内存往返。

**fused_ffn.wgsl**：将门控、上采样、SiLU 激活融合为单通道计算。

**add_norm.wgsl**：将残差连接和 RMSNorm 层归一化合并。

这种融合策略不仅减少了调度开销，更重要的是让每一层的前向传播都能在可读的代码中完整呈现。开发者可以打开任意一个 WGSL 文件，理解它在做什么，甚至修改它——没有编译器的黑箱，没有生成的机器代码。

## 完整的推理流水线

Zero-TVM 实现了现代 LLM 推理所需的全部组件，全部以人类可读的代码呈现：

**32 层 Transformer 解码器**：每层包含融合 QKV、注意力、融合 FFN、添加归一化四个核心调度。

**分页 KV 缓存**：采用 vLLM 风格的页表管理，32 层 × 257 页 × 196,608 字节/页，总计约 1.6 GB 缓存。支持可选的 int8 KV 量化路径，可减半内存占用。

**int4 反量化矩阵乘法**：这是计算密集型核心，Zero-TVM 提供了多种变体——基线版、子群归约版、分块版（rows×4 和 rows×8），以及用于语言模型头的 f32 输出版本（保证采样稳定性）。

**RoPE 位置编码**：旋转位置编码在融合 QKV 内核中就地计算。

**贪婪解码采样**：简单的 argmax 调度，暂不支持温度、top-k、top-p 等高级采样策略——这是为了保持「最小栈」的诚实性而有意省略的。

**BPE 分词器**：约 280 行 TypeScript 从零实现，包括词汇查找、合并表、元空间前缀、字节回退、SentencePiece 十六进制字节解码。虽然不完全匹配 HuggingFace 的完整预处理管道，但对于正常英文对话已足够准确。

## 性能基准：22% 的差距意味着什么

在 M2 Pro、Chrome 120+、相同 Phi-3-mini-q4f16_1 权重的条件下，Zero-TVM 的稳定解码速度约为 40 tok/s，而 WebLLM 为 51 tok/s。22% 的性能差距看似显著，但考虑到以下因素，这个结果极具启发性：

首先，WebLLM 的 TVM 管道经历了大量的自动调优，针对特定硬件生成了 85 个专门优化的内核。Zero-TVM 的手写内核没有进行任何自动调优，仅依靠合理的默认参数。

其次，22% 的差距远小于传统认知中「手写内核 vs 编译器优化」的预期。这表明，对于解码器-only 的 LLM 架构，编译器的大部分复杂度预算并没有转化为相应的性能收益。昂贵的部分确实是矩阵乘法、注意力和 int4 反量化，而其他一切都是「管道」——约 10 个内核的管道，而非 85 个。

项目作者在 BENCH.md 中详细记录了测试方法论和原始数据，包括三个被测量后放弃的优化实验：三种 QKV 分块策略（1152×32、2304×32、2304×64 工作组配置）在 M2 Pro 上全部倒退；提示查找投机解码的 CPU 模拟显示接受率不足以支持实现。这种「测量优先」的诚实态度值得赞赏。

## 可审计性：项目的真正价值

Zero-TVM 的最大价值可能不在于性能，而在于**可审计性**。每一个 FLOP 都在你可以打开的文件中；每一个 GPU 缓冲区都有人类可读的标签；每一次调度都在 chat.ts 或 engine-core.ts 中有注释记录。

如果你想：
- 为某一层添加仪器测量
- 测试新的注意力模式
- 尝试不同的内核融合策略
- 教某人浏览器 LLM 推理的底层原理

这里没有编译器挡路——只有 WGSL 和几百行 TypeScript 来编排它。这种透明度对于研究、教育、以及构建可信的 AI 系统都具有重要价值。

## 架构演进：从捕获到替换

项目的目录结构本身就是叙事弧线。每个页面代表一个里程碑：

1. **index.html → src/main.ts**：基线 WebLLM，未修改
2. **compiler-chat.html → src/compiler/chat-v2.ts**：中间阶段，捕获 TVM 调度并用自定义着色器重放其中 279 个
3. **zero-tvm.html → src/zero-tvm/chat.ts**：最终成果，所有 342 个调度被替换，不再接触 WebLLM
4. **validate.html → src/zero-tvm/validate.ts**：多提示烟雾测试，驱动 engine-core.ts
5. **webllm-bench.html → src/webllm-bench/main.ts**：诚实检查，WebLLM 与本地权重对比

此外，项目还提供了调度时间线可视化（demo.html）、全部 85 个 TVM 生成 WGSL 的捕获浏览（shaders.html）、逐着色器正确性测试（test-shaders.html）等工具。

## 技术限制与诚实声明

Zero-TVM 的 README 以罕见的透明度列出了当前限制：

**模型专用性**：目前仅支持 Phi-3-mini-4k-instruct Q4。架构常量（D=3072, HEADS=32, HEAD_DIM=96 等）不仅定义在 compiler.ts 中，还作为整数字面量硬编码在大多数着色器的地址运算中。移植到其他架构需要逐着色器重写偏移量和步幅。

**GPU 内存需求**：约 3.6 GB（1.8 GB 权重 + 1.6 GB KV 缓存）。在 4 GB 集成 GPU 上会 OOM。可通过降低 MAX_PAGES 或启用 int8 KV 来缓解。

**WebGPU 要求**：需要 Chrome/Edge 支持 WebGPU 和 shader-f16 特性。Safari 的 WebGPU 目前不暴露 shader-f16。

**分词器限制**：手写的 BPE 实现不完全匹配 HuggingFace 的完整预处理管道，在表情符号、特殊 Unicode 或某些标点模式上可能偏离。

**贪婪解码**：仅支持 argmax 采样，无温度、top-k、top-p、重复惩罚等高级采样策略。

**顺序预填充**：每个提示词令牌都经过完整解码路径。适合聊天长度的提示；已提供批处理预填充的着色器但未接入解码循环。

**双解码实现**：engine-core.ts 是参考实现（validate.html 使用）；chat.ts 是实验性单体，包含渐进式流式传输、融合 QKV、int8 KV 等增强功能。两者都正确，但尚未统一。

## 使用与构建

运行 Zero-TVM 需要现代 Chrome 或 Edge，启用 WebGPU 和 shader-f16。首次加载时，浏览器会从 HuggingFace 下载约 1.8 GB 的 Phi-3-mini Q4 权重并缓存在 OPFS（源私有文件系统）中，后续加载即时完成。

开发构建：
```
npm install
npm run dev
```

生产构建：
```
npm run build  # → dist/
```

构建输出包含多个页面：index.html（着陆页）、zero-tvm.html（聊天演示）、compiler-chat.html、demo.html（调度可视化）、validate.html（多提示测试）、webllm-bench.html（对比测试）、architecture.html、docs.html。

zero-tvm.html 支持 URL 参数进行 A/B 测试：?sg=0 禁用子群着色器，?kv8=1 启用 int8 KV 缓存，?qkvtile=1 尝试分块 QKV 变体等。

## 开源与引用

Zero-TVM 采用 MIT 许可证开源。如果这个项目对你的研究或写作有用，作者建议按以下格式引用：

Gunaydin, A. B. (2026). Zero-TVM: Phi-3 in a browser on hand-written WGSL shaders. https://zerotvm.com | https://github.com/abgnydn/zero-tvm

## 结语

Zero-TVM 是一个研究性质的项目，但它提出的问题具有广泛意义：在 AI 基础设施日益复杂的今天，我们是否过度依赖编译器和框架？对于特定的、定义明确的工作负载，手写内核是否可能提供「足够好」的性能，同时带来不可估量的透明度和可控性？

22% 的性能差距是一个代价，但对于某些场景——教育、研究、可审计性要求高的部署、或者只是对「我的代码在做什么」的好奇——这个代价可能是值得的。正如 Karpathy 的 llm.c 证明了「你可以用 C 写 GPT-2」，Zero-TVM 证明了「你可以在浏览器中用 WGSL 和 TypeScript 运行 Phi-3」——而且跑得相当不错。
