Esta mañana, en Hacker News, vi Moebius: 0.2B Lightweight Image Inpainting Framework with 10B-Level Performance, describiendo un modelo de inpainting pequeño pero efectivo: un modelo donde se pueden marcar regiones de una imagen para eliminarlas y el modelo imagina lo que debería rellenar el espacio. El modelo liberado requería PyTorch y NVIDIA CUDA, pero como se describía a sí mismo como de 0,2B parámetros, decidí intentar hacerlo correr usando WebGPU en un navegador. TL;DR: lo conseguí, y se puede probar el demo en simonw.github.io/moebius-web/. Sigan leyendo para los detalles.
¿Cómo quedó la herramienta final?
Se puede abrir cualquier imagen (las no cuadradas quedan letterboxed), destacar las áreas a eliminar, hacer clic en el botón "Run inpaint" y esperar a que el modelo haga su magia.
Un proyecto paralelo con agentes
Mi proyecto principal del día era aterrizar una funcionalidad mayor en Datasette: una UI para crear y alterar tablas, como continuación de la función para insertar y editar filas que liberé la semana pasada.
Estaba trabajando en eso con Codex Desktop (acá el PR) y me encontraba pasando 5-10 minutos girando los pulgares mientras esperaba que completara un refactor mediano o pulir detalles de UI.
(Lo divertido de los agentes de programación es que mientras más difícil es el problema, más tiempo tenés para distraerte mientras esperás que terminen de procesar).
Así que decidí levantar Claude Code en una terminal y ver hasta dónde podía llegar porteando Moebius a la web.
Investigación agéntica para arrancar
Mi primer paso fue preguntarle al Claude regular sobre la factibilidad del proyecto. En Claude.ai, que tiene la capacidad de clonar repos desde GitHub:
"Clona https://github.com/hustvl/Moebius/ y dime si publicaron código y pesos para correr este modelo en algún lugar"
(Aún no había visto el link a los pesos, está escondido en la sección "News").
"Para Moebius, ¿qué opciones hay para correrlo ahora mismo? ¿Solo Python y NVIDIA CUDA u otras opciones también?"
"Reflexiona sobre la factibilidad de portarlo a Transformers.js o similar y correrlo en un navegador"
Me gusta pedirle a los modelos que "reflexionen sobre X", es la forma más corta que encontré de expresar que quiero que contemplen un problema sin entregarles un objetivo concreto.
Acá está el transcript de ese chat. Copié la última respuesta y la guardé como research.md para que Claude Code la leyera después.
Claude sugirió usar ONNX Runtime Web sobre el backend WebGPU, la capa por debajo de la librería Transformers.js que yo había sugerido.
Eso fue suficiente para convencerme de que valía la pena soltar a Claude Code y ver hasta dónde llegaba.
Generalmente arranco proyectos así reuniendo toda la información que el agente pueda necesitar. Como no esperaba que el proyecto funcionara, hice todo en mi carpeta /tmp:
cd /tmp
mkdir Moebius
cd Moebius
# Tomar el código Python de Moebius
git clone https://github.com/hustvl/Moebius
# Y los pesos del modelo (Claude lo descubrió):
GIT_LFS_SKIP_SMUDGE=0 git clone https://huggingface.co/hustvl/Moebius Moebius-weights
# Finalmente un par de librerías que podríamos usar:
git clone https://github.com/huggingface/transformers.js
git clone https://github.com/microsoft/onnxruntime¿Qué le pidió a Claude Code?
Creé un directorio para el resto del proyecto y corrí git init ahí para que Claude pudiera empezar a commitear notas:
mkdir /tmp/Moebius/moebius-web
cd /tmp/Moebius/moebius-web
git init
# Copiar ese research.md de antes
git add research.md
git commit -m "Initial research by Claude Opus 4.8"Levanté una instancia de claude en la carpeta /tmp/Moebius, el nivel por encima de todos los materiales de investigación. Le pedí:
"Lee ./moebius-web/research.md. Tu objetivo es portar este modelo a ONNX y WebGPU para que podamos correrlo directamente en un navegador, con una UI simple"
Cuando empezó a trabajar, le dejé caer este follow-up (typos incluidos):
"Bulid esto en /tmp/Moebius/moebius-web y commiteá temprano y seguido, mantené también un archivo notes.md ahí con notas de lo que vas descubriendo, también empezá escribiendo un plan.md ahí y actualizá ese plan a medida que trabajás"
Suelo pedirle a los agentes que mantengan notas así: el resultado final es a menudo interesante, tanto para mí como para la próxima sesión que toque el mismo proyecto.
Lo arranqué y volví a mi proyecto principal, revisando ocasionalmente cómo iba Claude. Cuando parecía que tenía algo que funcionaba, le pedí:
"Decime qué URL puedo visitar en mi propio navegador para probar esto"
Lo probé en Chrome y le pegué algunos errores (y capturas de errores) de vuelta en Claude Code.
Después de algunas rondas de eso, teníamos algo que parecía funcionar. Tiempo de ponerlo en internet para que otros pudieran usarlo.
"¿Cómo publicaríamos esto a Hugging Face de modo que los pesos del modelo estén ahí y el demo HTML aparezca en Hugging Face spaces?"
Claude Code sabe usar la herramienta hf CLI, así que creé un repo de modelo en Hugging Face, creé un token que pudiera escribir a ese repo y lo dejé en un archivo /tmp/Moebius/token.txt para que Claude lo usara.
Publicó los 1,24 GB de pesos ONNX convertidos en huggingface.co/simonw/Moebius-ONNX.
¿Y la caché del navegador?
Había visto otros demos cargando pesos al navegador desde Hugging Face antes, así que sabía que era posible. Decidí hostear mi propio código frontend en GitHub Pages:
"Quiero publicar la carpeta moebius-web a GitHub, menos los archivos grandes (así que tal vez menos la carpeta models/), de modo que cuando active GitHub Pages para ese repo, navegar a https://simonw.github.io/moebius-web/ sirva la UI"
Decirle la URL final fue importante por si necesitaba arreglar las URLs en los demos que estaba construyendo para que funcionaran cuando se desplegaran a producción.
Después de algunas rondas más de iteración, en medio de mi proyecto principal, llegamos a una versión deployada y funcionando.
Excepto que... cada vez que recargaba la página parecía descargar ~1,3 GB de pesos del modelo. ¡La caché del navegador parecía bastante importante para esto!
"¿Algo inteligente que podamos hacer con service workers o similar para ayudar a cachear esto? Parece recargar todo cada vez, me preocupa que haya algo raro sobre cómo HF redirige que signifique que no nos beneficiamos del caché del navegador"
Sabía que los proyectos Transformers.js manejan esto correctamente, así que agarré una copia del demo Whisper Web, la dejé en /tmp/Moebius/whisper-web y dije:
"mirá en /tmp/Moebius/whisper-web (con un subagente) y veé cómo hacen esto"
Ese proyecto estaba totalmente ofuscado, archivos JavaScript built, así que pensé que usar un subagente evitaría gastar el resto del contexto de mi token de alto nivel descifrando esos archivos.
Claude descubrió que usaba caches.open("transformers-cache") (la CacheStorage API) y lo agregó a nuestro proyecto.
¿Qué se aprendió de todo esto?
Esto definitivamente cuenta como vibe coding: no miré una sola línea de código del proyecto, restringiendo mi input a testing, sugerir pequeñas mejoras (como una barra de progreso para las descargas grandes) y apuntar al modelo en la dirección de ejemplos de cómo quería que funcionaran las cosas.
Como no escribí nada de código, lo que aprendí sobre las tecnologías subyacentes (WebGPU, ONNX y el modelo Moebius en sí) fue muy limitado.
Como es usual con este tipo de proyectos, las cosas más importantes que aprendí tuvieron que ver con qué era posible:
- Claude Opus 4.8 es capaz de convertir un modelo PyTorch a ONNX, publicar el resultado a Hugging Face y luego construir una aplicación web e interfaz que pueda cargar y ejecutar ese modelo.
- Chrome, Firefox y Safari ya son capaces de correr este tipo de modelo: lo probé en los tres.
- La CacheStorage API funciona con archivos de modelo de ~1,3 GB.
- Lo que significa que podemos tener inpainting como una funcionalidad de una aplicación web solo cliente. Si nuestros usuarios pueden tolerar la descarga de 1,3 GB.




