☰
Handwired GPU 搜索实战:WebGPU 毫秒级检索 10 万份 JSON 文档的全链路深度解析
2026/10/20 2:26:22 网站建设 项目流程

【免费下载链接】handwired

Turn your regular JavaScript code into a neural net.

项目地址:https://gitcode.com/gh_mirrors/ha/handwired
点击查看免费下载

Handwired GPU 搜索是本仓库最硬核的 demo:它把普通的 JavaScript 代码编译成"手写的神经网络",再交给 WebGPU 并行执行,从而在浏览器里对 100,000 份 JSON 文档实现毫秒级正则检索。你每敲下一个字符,GPU 就在 11~16 ms 内扫完全部 10 万份文档,给出每份文档的匹配次数,全程不需要训练、结果与 CPU 逐位精确一致。

上图是 handwired 演示站点(bun start后访问/json-search/即为 GPU 搜索页):左侧 Monitor 面板展示的是"程序本身"——每一条线(wire)、每一层算子(affine / ReGLU),这就是"把 JS 编译成神经网络"的直观样子。

什么是 Handwired:把 JavaScript 直接变成神经网络

大多数项目用神经网络需要数据、需要训练。Handwired 反其道而行:权重是手写的,不需要任何训练,输出却精确无误。

它的原理非常朴素。程序无非是 if-else、循环和状态,而分支和比较都可以用"加法和 ReLU"表达:

// JS: (a > 4) ? 10 : 12 // Handwired: 用 ReLU 门控同时算出两个分支,再挑选一个 const above4 = Math.max(0, a - 4) - Math.max(0, a - 5) const result = above4 * 10 + (1 - above4) * 12

整段代码最终被收集成一张巨大的矩阵乘法 + ReLU 网络。因为只用了这两种"极简原语":

  • ✅全程序保持可微——权重解冻就能真的做端到端训练;
  • ✅权重不用训练——人写代码,网络只是执行形式;
  • ✅GPU 优化技术全部适用——算子融合、稀疏化、重排都对这种极简原语极其友好。

而 JSON 搜索正是第三个用途的最佳证明:用单线程写法写出的 JS 代码,获得了 GPU 级别的并行度。核心代码在 src/json-search/,官方教程见 docs/tutorial.md。

任务设定:10 万份 JSON 文档是怎么备好的

演示页由 src/json-search/documents.ts 生成 10 万份"真实感"文档,每份是一个合法 JSON(最长 512 个 code point,平均约 300 字符):

维度内容
7 种形状包清单、用户记录、日志事件、编辑器配置、订单、地图要素、传感器读数
4 种写法单行、逗号后加空格、2 空格缩进、Tab 缩进
16 种缺陷缺引号、尾逗号、坏转义、单引号等(demo 页不启用)

关键设计:所有选择都来自哈希而不是Math.random()。你可以直接生成"第 73,512 份"而不用先生成前面所有文档,页面、测试、测量脚本三方因此天然一致。

上传到 GPU 时,每份文档被写成 513 个 float32 值(512 个字符 + 文本长度),即 2,052 字节;10 万份共205.2 MB,一次writeBuffer上卡,之后永不再动。文档的单元(cells)写入逻辑在 src/json-search/program.ts。

💡 单份文档用 GPU 是亏的——一次 GPU 往返就要 0.6 ms 以上;10 万份才值得:每份文档执行完全相同的算子、且互不读取对方的数据,一次 compute pass 全部跑完。

两个 WebGPU 程序:JSON 高亮器 + 正则搜索

页面加载时先跑一遍高亮器(src/json-search/json-step.ts),再在每次击键时跑一遍搜索器(src/json-search/search-step.ts)。

高亮器:每个字符显示为什么

高亮器是一个 8 状态语法状态机 + 分词器,对每份文档的 512 个位置各做一次"步进",输出每个字符的显示类型(键、字符串值、数字、转义、错误……)以及valid、firstErrorPosition、firstErrorKind。每步 37 个算子(其中 12 个查表),一份文档共 18,961 个算子。

高亮结果有两个用途:文档着色,以及让搜索只读键或只读字符串值(scope 参数)。

搜索器:把正则编译成自动机

src/json-search/regex/pattern.ts 把正则子集(字面量、.、\d、\w、字符类、?*+|和分组)编译成确定自动机,每读一个字符或字符类占一个状态,加 1。比如error是 6 个状态,connection reset是 17 个状态。

自动机的转移矩阵、接受状态、字符类表全部存在 **cells(GPU 上的共享缓冲区)**里。这意味着:

  • 新模式 = 新写一组 cells(8 状态模式下 1,097 个值,JS 侧仅 0.03~0.1 ms);
  • GPU 代码里不含任何模式——页面加载时就把 8 / 16 / 32 状态三种程序全部编译好(shader 各 7.7k~14.3k 字符,共 4 个 shader),击键零编译等待;
  • 模式跑在能装下它的最小程序上,超出 32 状态则返回明确的错误信息而不是算错。

循环程序(loop program):shader 长度与文档数无关

两个程序都是 src/json-search/loop-program.ts 描述的"循环程序":把"一个位置的工作"用 builder 代码编译成一个小程序,shader 里只写一遍、放在for循环内跑 512 次。这个设计的威力在于编译时间:

写法shader 编译时间
把 256 个位置全部展开写107~130 秒
循环形式(每位置写一遍)0.02~0.05 秒

循环体的状态(寄存器和字段)在 docs/patterns.md 中有完整模式讲解。

毫秒级检索的关键:bit-emit 着色器优化器

搜索步每位置要对"每对状态"做一次转移查表,算子随状态数平方增长。最初 src/json-search/loop-emit.ts 把每个算子写成一个 f32 语句,效果不理想。于是有了第二个代码生成器 src/json-search/bit-emit.ts——它证明某些值必然是整数或 0/1 位后,把算子改写成最便宜的位运算形式:

  • 证明是整数的值用i32,证明是 0/1 的用 bit:ReLU 门relu(a-b)变为a & (b ^ 1u);
  • 多个"同一次查表"的门合并成一个 32 位字的 AND:8 次查表变 1 条指令;
  • 对所有状态相同的门变成一个字操作:reached & (notReach ^ 255u)。

效果(10 万份文档、连续搜索、Apple M5 Max + Chrome 155):

程序容量loop-emit 语句数bit-emit 语句数loop-emit 耗时bit-emit 耗时
8 状态5031477.8 ms4.6 ms
16 状态1,37517169 ms4.7 ms
32 状态4,655219945 ms5.2 ms

优化后,模式长短几乎不再影响耗时——8 到 32 状态都在 4.6~5.2 ms 之间。完整推导和"规则边界"记录在 lab/json-search/million/README.md。

实测性能对比:GPU vs 原生 RegExp vs CPU

以下是 lab/json-search/README.md 中"Is the GPU clearly faster?"一节的诚实结论(10 万份文档,单线程对比):

方案典型模式(如error)长模式(32 状态)灾难模式([a-z]+)+x
原生 RegExp(单线程)8~28 ms8~29 ms2.1 s(回溯)
Handwired GPU(连续)4.5~5.2 ms5.2 ms5~12 ms
Handwired GPU(击键间隔 200ms+)11~16 ms13.2 ms同上
同一程序在 CPU(生成的 JS)13 s3 分钟3 分钟
  • 页面每份模式都同时显示两种耗时,不吹"全面碾压 RegExp"——对日期\d\d\d\d-\d\d-\d\d这类 RegExp 最快的模式,单线程 RegExp 反而略快;
  • 但 RegExp 会回溯,([a-z]+)+x需要 2.1 秒,而 GPU 搜索是固定自动机,永远 5~12 ms,没有任何模式会让它变慢;
  • 多 worker 对比(8 个 worker 1.9~6.7 ms)GPU 不算更快,作者对此也如实记录。

其他关键数字:文档从导航开始 0.54 s 就绪,首个搜索结果 0.56 s 上屏;滚动只搜窗口内文档,耗时 0.6~2.1 ms;JS 侧静息内存 140 MB,GPU 侧 410 MB。

上图来自 lab/ui-exploration/pixel-output/:一个 HN 讨论线程,从布局到每个像素的字符字形,全部由"手写神经网络"在 WebGPU 上算出——这正是 Handwired"任何 JS 都能变网络"思路的另一面。

全链路解析:一次击键的 11 ms 里发生了什么

以在输入框敲下一个字符为例,链路如下(状态机在 src/json-search/demo.ts,GPU 宿主在 src/json-search/gpu/host.ts):

击键 → JS 侧编译模式为自动机(0.03~0.1 ms) → 把 1,097 个模式 cells 上传 GPU → 一次 compute pass:10 万份文档同时跑同一自动机 · 反向 pass:算"从该位置向后能否走到接受态" · 正向 pass:判定匹配起点/延续,累计匹配数 → 读回:每份文档 1 个匹配数 + 窗口内文档的逐字符标记 → 画地图:命中文档点亮铜色格子(11~16 ms)

几个值得注意的工程细节:

  1. 双向 pass 求"最左最长"匹配。最长匹配要读完才知道终点,而程序不能回头,所以搜索分反向 + 正向两遍;
  2. 搜索只读高亮器留在 GPU 上的输出缓冲——高亮器只在文档加载时跑一次,每次击键只跑搜索;
  3. scope 掩码用 3 个额外算子把非目标字符替换成 -1(即"文本之外"的值),实现"只搜字符串值";
  4. 请求并发控制:同一时刻只有一个求值在飞,旧请求直接返回undefined,页面把结果当作"下一帧的输入"。

模块级 API 在 src/json-search/searcher.ts,几行即可接入自己的页面:

const searcher = await createGpuSearcher(device, texts) // 或 createCpuSearcher(texts) const result = await searcher.search({pattern: '(the|an?) [a-z]+', scope: 'strings'})

自己动手:3 条命令上手

git clone https://gitcode.com/gh_mirrors/ha/handwired cd handwired && bun install bun start # 演示站点,GPU 搜索页在 /json-search/
  • 页面顶部有All / Keys / String values三个 scope 开关和GPU / CPU后端开关;
  • CPU 开关打开后,同一套循环程序以生成的 JavaScript 在 worker 里分批跑,地图从上往下逐渐点亮,行内实时显示进度与耗时;
  • 没有 WebGPU 的浏览器自动走 CPU 路径;
  • 运行测试(184 个、无需 GPU,约 45 秒):bun test --timeout 30000 tests/json-search;类型检查与 lint:bun run check。

正确性如何保证:精确整数 + 独立参考实现

"神经网络做精确计算"听起来不放心,Handwired 的测试体系给了答案(tests/json-search/):

  • 两个程序里每个值都是 bit 或小整数,GPU 上任意求和顺序都精确,因此对比可以逐位相等;
  • 参考实现是按规则独立手写的(真实的栈、真实的JSON.parse、逐子串new RegExp),不是从程序抄出来的;
  • 附带一个严格的WGSL 解释器(tests/json-search/wgsl-interpreter.ts),逐条执行两个 writer 生成的着色器源码,与 CPU 执行器对拍;
  • 真机 GPU 上,每次测量运行都读回 7 亿~11 亿个值与 CPU 比对,无一不同(仅限 Apple M5 Max + Metal 这一组合,作者注明)。

总结

Handwired 的 JSON 搜索 demo 展示了一条反直觉的路径:不手写 kernel、不训练模型,只把普通 JS 编译成"加权求和 + ReLU",再交给 WebGPU 的现有优化体系。三个层次的设计层层递进——

  1. 循环程序让 shader 长度与数据规模解耦,编译从 130 秒降到 50 毫秒;
  2. bit-emit用可证明的位级改写把 32 状态搜索从 945 ms 压到 5.2 ms;
  3. cells + 固定自动机让模式成为纯数据,击键时零编译、零回溯、耗时恒定。

最终,一份单线程写法的 JS 程序在 10 万份 JSON 上拿到了 4.5~16 ms 的检索延迟,并与手写内核同速、与 CPU 结果逐位一致。如果你想继续深挖,推荐顺序:docs/tutorial.md(cells 与 builder 入门)→ src/json-search/README.md(两个程序的逐步拆解)→ lab/json-search/README.md(全部实测表格与诚实的边界讨论)。

【免费下载链接】handwired

Turn your regular JavaScript code into a neural net.

项目地址:https://gitcode.com/gh_mirrors/ha/handwired
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询