将 Moebius 修到浏览器中
Simon Willison··作者 Simon Willison
关键信息
原始 Moebius 版本需要 PyTorch 和 NVIDIA CUDA,而浏览器版本改用 WebGPU,并且根据 Claude 的建议使用了基于 WebGPU 后端的 ONNX Runtime Web。这个演示支持打开图片、圈选要移除的区域,并在浏览器中进行修补,非方形图片会以留边方式显示。
资讯摘要
Simon Willison 说,他最初是在 Hacker News 上看到 Moebius 的。Moebius 是一个 0.2B 规模的轻量级图像修补框架,用于把图像中标记出来要移除的区域补全成合理内容,项目还宣称虽然模型很小,却能达到接近 10B 级别的效果。作者看到其发布版本看起来需要 PyTorch 和 NVIDIA CUDA,于是开始考虑能否把它改造成能在浏览器中运行。由于模型只有 0.2B 参数,他觉得这个移植值得一试,于是着手构建 WebGPU 版本。最终结果是一个可用的浏览器演示,部署在 `simonw.github.io/moebius-web/`。
在这个演示里,用户可以加载图片、圈选要移除的区域,然后点击按钮在浏览器本地执行修补。Willison 还描述了自己如何先用 Claude 做了一些探索性提问,包括询问模型代码和权重是否已发布、当前有哪些运行选项,以及是否适合移植到 Transformers.js 或类似的浏览器运行时。Claude 建议使用带 WebGPU 后端的 ONNX Runtime Web,这一方案位于 Transformers.js 之下的底层,正是这个建议让他决定继续推进。文章还提到,这个项目是他在处理另一个任务的空档里并行完成的;当时他在 Datasette 里用 Codex Desktop 开发一个关于创建和修改表结构的新功能。

资讯正文
今天早上,我在 Hacker News 上看到 Moebius:0.2B Lightweight Image Inpainting Framework with 10B-Level Performance,介绍了一个小巧但有效的图像修补模型——这种模型可以让你标记图像中要移除的区域,然后由模型去想象那里应该填入什么。发布的模型需要 PyTorch 和 NVIDIA CUDA,不过既然它自称只有 0.2B 参数,我决定试着让它在浏览器里通过 WebGPU 运行。简单来说:我已经把它跑起来了,你可以在 simonw.github.io/moebius-web/ 试用这个演示。下面是详细过程。
这是成品工具的视频演示:
你可以在其中打开任意图像(非正方形图像会加黑边适配),高亮要移除的区域,点击“Run inpaint”按钮,然后等待模型施展魔法。
我今天的主项目,是在 Datasette 里落地一个重大功能:用于创建和修改表的界面,这是我上周发布的插入和编辑行功能的后续。
我当时是在 Codex Desktop 里做这件事的(这里是 PR),经常会遇到这样一种情况:为了等待它完成一次中等规模的重构,或者为某个 UI 改动补上最后的修饰,我会花上 5 到 10 分钟无所事事地转手指。
(编码代理有个有趣的地方:问题越难,你在它们完成计算前就越有时间分心!)
于是我决定在一个终端窗口里启动 Claude Code,看看我能在把 Moebius 移植到网页端这件事上推进到什么程度。
我先做了一点代理式研究来启动这个项目。
第一步,是向普通版 Claude 询问这个项目的可行性。在 Claude.ai 里,它具备从 GitHub 克隆仓库的能力:
`Clone https://github.com/hustvl/Moebius/ and tell me if they published the code and weights to run this model anywhere`
(我当时还没注意到权重的链接,它藏在“News”版块里。)
然后我又问:
`For Moebius what are the options for running it right now - Python and NVIDIA CUDA only or other options too?`
接着:
`Muse on the feasibility of porting it to Transformers.js or similar and running it in a browser`
我喜欢让模型“沉思某个问题”,这是我找到的最简短的表达方式,用来说明我希望它替我思考一个问题,而不是先给它一个明确的目标。
这里是那段聊天记录。我把最后的回答复制出来,保存成 `research.md`,供 Claude Code 之后阅读。
Claude 建议使用基于 WebGPU 后端的 `ONNX Runtime Web`——也就是我之前建议的 `<a href="https://huggingface.co/docs/transformers.js/en/index">Transformers.js</a>` 库下面那一层。
这已经足以说服我,值得放手让 Claude Code 去试试,看看它能推进到什么程度。
我通常会先尽可能收集这个编码代理可能需要的所有信息,才开始做这类项目。由于我并不指望这个项目真的能跑通,所以我把所有内容都放在了我的 `/tmp` 文件夹里:
`cd /tmp`
`mkdir Moebius`
`cd Moebius`
`# 抓取 Moebius 的 Python 代码`
`git clone https://github.com/hustvl/Moebius`
`# 以及模型权重(这是 Claude 发现的):`
`GIT_LFS_SKIP_SMUDGE=0 git clone \
https://huggingface.co/hustvl/Moebius Moebius-weights`
`# 最后再拉几个我们可能会用到的库:`
`git clone https://github.com/huggingface/transformers.js`
`git clone https://github.com/microsoft/onnxruntime`
### 让 Claude Code 开工
我为项目的其余部分创建了一个目录,并在那里运行了 `git init`,这样 Claude 就可以开始提交代码笔记:
`mkdir /tmp/Moebius/moebius-web`
`cd /tmp/Moebius/moebius-web`
`git init`
`# 把前面那个 research.md 复制进来`
`git add research.md`
`git commit -m "Initial research by Claude Opus 4.8"`
我在 `/tmp/Moebius` 文件夹里启动了一个 `claude` 实例,这个位置位于我为它准备的所有研究材料的上一级目录。我输入了提示词:
`Read ./moebius-web/research.md - your goal is to port this model to ONNX and WebGPU so we can run it directly in a browser, with a simple UI`
当它开始工作时,我又补了一条后续提示(包括拼写错误):
`Bulid this in /tmp/Moebius/moebius-web and commit early and often, also maintain a notes.md file in there with notes about what you figure out along the way - also start by writing out a plan.md in there and update that plan as oy work too`
我经常会让 agents 记下这样的笔记——最后的结果通常都很有意思,无论对我自己,还是对下一个接手同一项目的 agent session 都是如此。下面就是这个项目结束时那个 <a href="https://github.com/simonw/moebius-web/blob/main/notes.md">notes.md 文件</a>的样子。
我先把它启动起来,然后回到我的主项目,只是偶尔过去看看 Claude 做得怎么样。等它看起来好像有点成果时,我就提示:
<code>告诉我我可以在自己的浏览器里访问哪个 URL 来试这个</code>
然后我就在 Chrome 里试了一下,并把一些错误信息(以及错误截图)粘回 Claude Code。
经过几轮这样的往返后,我们得到了一个看起来能工作的东西!是时候把它放到网上,让其他人也能用了。
<code>我们该如何把这个发布到 Hugging Face,既让模型权重放上去,又让 HTML demo 出现在 Hugging Face Spaces 里?</code>
Claude Code 知道如何使用 <code>hf</code> CLI 工具,所以我先在 <a href="https://huggingface.co/">Hugging Face</a> 上创建了一个模型仓库,然后<a href="https://huggingface.co/settings/tokens">创建了一个 token</a>,让它可以向那个仓库写入内容,并把它放到 <code>/tmp/Moebius/token.txt</code> 文件里,这样 Claude 就能使用了。
它帮我把 1.24GB 的已转换 ONNX 权重发布到了 <a href="https://huggingface.co/simonw/Moebius-ONNX">huggingface.co/simonw/Moebius-ONNX</a>。
我以前见过其他 demo 从 Hugging Face 把权重加载到浏览器里,所以我知道这是可行的。我决定把自己的前端代码托管在 GitHub Pages 上,于是我说:
<code>我想把 moebius-web 文件夹发布到 GitHub,但不要大文件(所以也许去掉 models/ 文件夹),这样当我为那个仓库开启 GitHub Pages 时,访问 https://simonw.github.io/moebius-web/ 就会提供这个 UI</code>
把最终 URL 告诉它很重要,以防它需要修正自己在构建 demo 时使用的 URL,这样部署到生产环境后才能正常工作。
在我继续做主项目的间隙,又经过几轮迭代后,我们得到了一个可用、已部署的版本!
不过……每次我重新加载页面时,它似乎都会下载大约 1.3GB 的模型权重。浏览器缓存对这件事显然很重要!
<code>有没有什么聪明办法可以用 serviceworker 或类似机制来帮助缓存这些东西?它每次都会重新加载,我担心 HF 的重定向方式可能有些奇怪,导致我们无法受益于浏览器缓存</code>
我知道 Transformers.js 项目可以正确处理这个问题,所以我拿了一份 <a href="https://huggingface.co/spaces/Xenova/whisper-web">Whisper Web</a> demo 的副本,把它放进 <code>/tmp/Moebius/whisper-web</code>,然后说:
<code>查看 /tmp/Moebius/whisper-web(用一个 subagent),看看他们是怎么做的</code>
那个项目完全经过混淆,生成的是 JavaScript 文件,所以我觉得用一个子代理,应该能避免我把剩下的顶层 token 上下文都花在解读那些文件上。
Claude 发现它在使用 `caches.open("transformers-cache")` ——也就是 [CacheStorage API](https://developer.mozilla.org/en-US/docs/Web/API/CacheStorage/open)——并且[把这一点添加到了我们的项目中](https://github.com/simonw/moebius-web/commit/05c1cbc4894460a70a8bc1718ac6d152219e0f28#diff-fb89c342dfa36f544a2d16a885b0f3d1d49f436a7d0eaeb80505f80a1f922603)。
我已经分享了这个项目的[完整 Claude Code 对话记录](https://gisthost.github.io/?58039ba5c1ca3ed177e8659168996ee4)(使用我的 [claude-code-transcripts](https://github.com/simonw/claude-code-transcripts) 工具发布)。
#### 我从这一切中学到了什么?
这当然算得上是 vibe coding:我一行代码都没看,只把输入限制在测试、提出一些小功能改进建议(比如给大文件下载加进度条),以及给模型指出一些我希望它实现方式的示例。
由于我自己没有写任何代码,我对底层技术——WebGPU、ONNX 以及 Moebius 模型本身——的了解都非常有限。
不过,像这类项目通常一样,我学到的最重要的东西主要是关于“什么是可能的”:
- Claude Opus 4.8 有能力把一个 PyTorch 模型转换成 ONNX,把结果发布到 Hugging Face,然后再构建一个可以加载并执行该模型的 Web 应用和界面。
- 现在 Chrome、Firefox 和 Safari 都能够运行这类模型——我在这三者上都试过了。
- CacheStorage API 可以处理大约 1.3GB 的模型文件。
- ……这意味着我们可以把图像修补(inpainting)做成一个纯客户端 Web 应用的功能!如果用户能接受 1.3GB 的下载的话。
我觉得我大概还是应该再多了解一点自己的项目。我打开了 [Claude.ai](https://claude.ai/),然后输入了:
> `Clone https://github.com/simonw/moebius-web/ and use it to teach me all about the model and ONNX and the process of converting a model to ONNX and WebGPU and basically everything I'd need to know in order to fully understand this repo`
这里是[对话记录](https://claude.ai/share/d11b8f2b-a52d-4ca2-be75-a710eaf18572)以及它创建的 [understanding.md](https://github.com/simonw/moebius-web/blob/main/understanding.md) Markdown 文件,我现在已经把它加入了 GitHub 仓库。我尤其觉得它对 ONNX 的解释很有启发:
> **ONNX**(Open Neural Network Exchange)是一种可移植、与框架无关的神经网络文件格式。一个 `.onnx` 文件本质上是把两样东西打包在一起:
> 1.
<strong>计算图</strong>——一种由<em>节点</em>组成的有向图,每个节点都是一个<strong>算子</strong>(<code>Conv</code>、<code>MatMul</code>、<code>Add</code>、<code>Einsum</code>、<code>Softmax</code>、<code>Gather</code>、<code>Resize</code>,等等),它们通过在彼此之间流动的命名张量连接起来。这就是前向传播的“配方”。</li>
<li>
<strong>权重</strong>——学习得到的参数张量(卷积核、嵌入表等),作为初始化器存储在同一个图中。</li>
</ol>
<p>关键在于,ONNX 以抽象方式描述<em>要计算什么</em>,而不是<em>如何计算</em>或<em>在什么硬件上计算</em>。算子集由一个<strong>opset</strong>编号进行版本化(这个仓库使用的是<strong>opset 18</strong>),它精确规定了有哪些算子以及它们的语义。</p>
</blockquote>
<p>事实证明,PyTorch 内置了导出为 ONNX 的机制,如<a href="https://github.com/simonw/moebius-web/blob/080be6e737ec976130e260d34707d7d9b7f63d5b/python/export_onnx.py#L91">export_onnx.py 里的这里</a>所示:</p>
<pre><span class="pl-s1">torch</span>.<span class="pl-c1">onnx</span>.<span class="pl-c1">export</span>(
<span class="pl-s1">dec</span>, (<span class="pl-s1">lat</span>,), <span class="pl-s1">dec_path</span>, <span class="pl-c1">opset_version</span><span class="pl-c1">=</span><span class="pl-s1">args</span>.<span class="pl-c1">opset</span>,
<span class="pl-c1">input_names</span><span class="pl-c1">=</span>[<span class="pl-s">"latent"</span>], <span class="pl-c1">output_names</span><span class="pl-c1">=</span>[<span class="pl-s">"image"</span>],
<span class="pl-c1">dynamic_axes</span><span class="pl-c1">=</span>{<span class="pl-s">"latent"</span>: {<span class="pl-c1">0</span>: <span class="pl-s">"B"</span>}, <span class="pl-s">"image"</span>: {<span class="pl-c1">0</span>: <span class="pl-s">"B"</span>}},
)</pre>
<p>Claude 还附带了一个<a href="https://github.com/simonw/moebius-web/blob/main/understanding.md#12-mini-glossary">实用术语表</a>和一张略有瑕疵的<a href="https://github.com/simonw/moebius-web/blob/main/understanding.md#10-putting-the-whole-pipeline-in-one-picture">ASCII 艺术图</a>,展示了模型管线是如何组合在一起的。</p>
<p>标签:<a href="https://simonwillison.net/tags/browsers">browsers</a>、<a href="https://simonwillison.net/tags/transformers-js">transformers-js</a>、<a href="https://simonwillison.net/tags/webgl">webgl</a>、<a href="https://simonwillison.net/tags/vibe-coding">vibe-coding</a>、<a href="https://simonwillison.net/tags/coding-agents">coding-agents</a>、<a href="https://simonwillison.net/tags/claude-code">claude-code</a>、<a href="https://simonwillison.net/tags/onnx">onnx</a></p>
来源与参考
收录于 2026-06-24