【免费下载链接】handwired
Turn your regular JavaScript code into a neural net.
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 状态 | 503 | 147 | 7.8 ms | 4.6 ms |
| 16 状态 | 1,375 | 171 | 69 ms | 4.7 ms |
| 32 状态 | 4,655 | 219 | 945 ms | 5.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 ms | 8~29 ms | 2.1 s(回溯) |
| Handwired GPU(连续) | 4.5~5.2 ms | 5.2 ms | 5~12 ms |
| Handwired GPU(击键间隔 200ms+) | 11~16 ms | 13.2 ms | 同上 |
| 同一程序在 CPU(生成的 JS) | 13 s | 3 分钟 | 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)几个值得注意的工程细节:
- 双向 pass 求"最左最长"匹配。最长匹配要读完才知道终点,而程序不能回头,所以搜索分反向 + 正向两遍;
- 搜索只读高亮器留在 GPU 上的输出缓冲——高亮器只在文档加载时跑一次,每次击键只跑搜索;
- scope 掩码用 3 个额外算子把非目标字符替换成 -1(即"文本之外"的值),实现"只搜字符串值";
- 请求并发控制:同一时刻只有一个求值在飞,旧请求直接返回
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 的现有优化体系。三个层次的设计层层递进——
- 循环程序让 shader 长度与数据规模解耦,编译从 130 秒降到 50 毫秒;
- bit-emit用可证明的位级改写把 32 状态搜索从 945 ms 压到 5.2 ms;
- 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.
相关推荐
ruflo AgentDB 向量检索实战:从 CLI 到 RAG 的子毫秒级语义搜索全链路
ruflo AgentDB 向量检索实战:从 CLI 到 RAG 的子毫秒级语义搜索全链路 本文以 ruflo 仓库中的 AgentDB Vector Sear
人工智能AI AgentAI 应用多智能体Agent 框架Agent 编排Agent 记忆MCP 服务CLI插件系统代码智能体毫秒级全文检索:开源搜索引擎 lnx 实战指南
毫秒级全文检索:开源搜索引擎 lnx 实战指南 你是否还在为项目中的搜索功能响应迟缓而烦恼?是否因复杂的查询语法望而却步?本文将带你全面掌握开源搜索引擎 lnx
实测百万文本块搜索:Memvid如何实现毫秒级语义检索?
实测百万文本块搜索:Memvid如何实现毫秒级语义检索? 在数据爆炸的时代,高效管理和检索海量文本信息成为开发者面临的重要挑战。Memvid作为一款基于视频的A
人工智能RAGAgent 记忆
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考