Porting the Moebius 0.2B image inpainting model to run in the browser with Claude Code

Simon Willison

Claude Code로 Moebius 0.2B 이미지 인페인팅 모델을 브라우저에서 실행되도록 포팅하기

원문은 Simon Willison님이 에 게재했습니다. 이 블로그 구독하기

오늘 아침 Hacker News에서 Moebius: 0.2B Lightweight Image Inpainting Framework with 10B-Level Performance라는 글을 봤다. 이미지에서 특정 영역을 지정해 지우면 모델이 그 빈 공간을 무엇으로 채울지 상상해 내는, 작지만 효과적인 인페인팅 모델에 대한 소개였다. 공개된 모델은 PyTorch와 NVIDIA CUDA가 필요했다지만, 모델 크기가 0.2B라고 소개된 것을 보고 브라우저에서 WebGPU를 이용해 직접 구동해보기로 했다. TL;DR: 결국 성공했고, 데모는 simonw.github.io/moebius-web/에서 직접 써볼 수 있다. 자세한 내용은 계속 읽어보자.

완성된 도구

완성된 도구의 영상 데모다:

이미지를 불러온 뒤(정사각형이 아닌 이미지는 레터박스로 표시된다) 지우고 싶은 영역을 칠하고 “Run inpaint” 버튼을 누르면 모델이 마법을 부릴 때까지 기다리면 된다.

병렬로 진행한 에이전트 사이드 프로젝트

오늘 나의 메인 프로젝트는 Datasette에 대형 기능을 적용하는 것이었다. 테이블을 생성하고 수정하는 UI로, 지난주에 공개한 행 삽입 및 편집 기능의 후속이었다.

Codex Desktop에서 그 작업을 하고 있었는데(관련 PR은 여기) 중간 규모의 리팩터링이나 UI 변경의 마무리 작업을 끝내기를 기다리며 5~10분씩 손가락만 만지작거리는 일이 잦았다.

(재밌는 점은 코딩 에이전트에서는 문제가 어려울수록 에이전트가 처리하는 동안 딴짓할 시간이 더 많아진다는 것이다!)

그래서 터미널 창 하나에 Claude Code를 띄워 Moebius를 웹으로 포팅하는 작업이 어디까지 가능한지 시험해 보기로 했다.

프로젝트를 시작하기 위한 에이전트 리서치

첫 단계로 일반 Claude에게 이 프로젝트의 실현 가능성을 물어봤다. GitHub에서 저장소를 클론할 수 있는 Claude.ai에서였다:

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

모델에게 “muse on X”라고 시키는 걸 좋아한다. 구체적인 목표를 주지 않고도 문제를 곰곰이 생각해 보라고 시키는 가장 짧은 표현이기 때문이다.

해당 대화 기록은 여기에 있다. 마지막 답변을 복사해 Claude Code가 나중에 읽을 수 있도록 research.md로 저장해 뒀다.

Claude는 내가 제안했던 Transformers.js 라이브러리보다 한 단계 아래 레이어인 ONNX Runtime Web의 WebGPU 백엔드를 사용할 것을 제안했다.

그 정도면 Claude Code를 풀어 어디까지 할 수 있는지 지켜볼 가치가 있다고 확신하기에 충분했다.

나는 이런 프로젝트를 시작할 때 보통 코딩 에이전트에 필요할 만한 정보를 최대한 모아두는 것부터 시작한다. 이 프로젝트가 실제로 동작할 거라 기대하지 않았기 때문에 모든 작업을 /tmp 폴더에서 진행했다:

cd /tmp
mkdir Moebius
cd Moebius
# Grab the Moebius python code
git clone https://github.com/hustvl/Moebius
# And the model weights (Claude figured this out):
GIT_LFS_SKIP_SMUDGE=0 git clone \
  https://huggingface.co/hustvl/Moebius Moebius-weights
# Finally a couple of libraries we might use:
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
# Copy in that research.md from earlier
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

나는 에이전트에게 이렇게 노트를 남기도록 자주 시킨다. 최종 결과물이 나 자신에게도, 같은 프로젝트를 이어받는 다음 에이전트 세션에게도 종종 흥미롭기 때문이다. 프로젝트가 끝났을 때 notes.md 파일은 이렇게 생겼다.

작업을 시작시켜 두고 메인 프로젝트로 돌아가 가끔씩 Claude가 어떻게 진행하고 있는지 확인했다. 뭔가 동작하는 것 같아 보이자 이렇게 물었다:

Tell me what URL I can visit in my own browser to try this

그리고 Chrome에서 직접 실행해 본 뒤 발생한 에러(와 에러 스크린샷)를 Claude Code에 붙여 넣었다.

몇 차례 반복 끝에 동작하는 것처럼 보이는 결과물이 나왔다! 이제 다른 사람들도 쓸 수 있도록 인터넷에 올릴 차례였다.

How would we publish this to Hugging Face such that the model weights were on there and the HTML demo would show up in Hugging Face spaces?

Claude Code는 hf CLI 도구를 다룰 줄 안다. 그래서 Hugging Face에 모델 저장소를 하나 만들고, 해당 저장소에 쓸 수 있는 토큰을 생성해 Claude가 사용할 수 있도록 /tmp/Moebius/token.txt 파일에 넣어 두었다.

Claude가 변환된 1.24GB짜리 ONNX 가중치를 huggingface.co/simonw/Moebius-ONNX에 대신 게시해 줬다.

다른 데모들이 Hugging Face에서 가중치를 브라우저로 불러오는 것을 본 적이 있어 가능하다는 건 알고 있었다. 내 프론트엔드 코드는 GitHub Pages에 호스팅하기로 하고 이렇게 말했다:

I want to publish the moebius-web folder to GitHub, minus the large files (so maybe minus the models/ folder), such that when I turn on GitHub Pages for that repo navigating to https://simonw.github.io/moebius-web/ serves the UI

최종 URL을 알려주는 것이 중요했다. 빌드 중인 데모의 URL을 프로덕션 배포 시에도 동작하도록 수정해야 할 수도 있기 때문이다.

메인 프로젝트를 병행하면서 몇 차례 더 반복한 끝에 동작하는 배포 버전을 만들었다!

그런데... 페이지를 새로고침할 때마다 약 1.3GB에 달하는 모델 가중치를 다시 다운로드하는 것 같았다. 브라우저 캐싱이 꽤 중요해 보였다!

anything clever we can do with serviceworkers or similar to help cache this stuff? It seems to reload every time, I am concerned that there might be something weird about the way HF redirects work that mean we don't benefit from browser caching

Transformers.js 프로젝트라면 이걸 제대로 처리할 수 있다는 걸 알고 있었다. 그래서 Whisper Web 데모를 복사해 /tmp/Moebius/whisper-web에 넣어 두고 이렇게 말했다:

look in /tmp/Moebius/whisper-web (with a subagent) and see how they do this

그 프로젝트는 빌드된 JavaScript 파일들이 완전히 난독화되어 있어서, 서브에이전트를 쓰면 최상위 토큰 컨텍스트를 그 파일들을 해독하는 데 소모하지 않아도 될 것 같았다.

Claude는 해당 프로젝트가 caches.open("transformers-cache")CacheStorage API — 를 사용하고 있음을 알아냈고, 이를 우리 프로젝트에 추가했다.

이 프로젝트의 전체 Claude Code 대화 기록을 공유해 뒀다(내 claude-code-transcripts 도구로 게시했다).

이 모든 것에서 무엇을 배웠나?

이건 확실히 바이브 코딩에 해당한다. 프로젝트의 코드를 한 줄도 보지 않았고, 내 역할은 테스트와 작은 기능 개선 제안(대용량 파일 다운로드용 진행률 표시줄 같은 것), 그리고 원하는 동작 방식의 예시를 모델에 알려주는 것으로 제한했다.

직접 코드를 작성하지 않았기 때문에 기반 기술인 WebGPU, ONNX, 그리고 Moebius 모델 자체에 대해 배운 것은 매우 제한적이었다.

이런 종류의 프로젝트에서 흔히 그렇듯, 가장 중요하게 배운 것은 무엇이 가능한가였다:

  • Claude Opus 4.8은 PyTorch 모델을 ONNX로 변환하고, 결과를 Hugging Face에 게시한 뒤, 해당 모델을 불러와 실행할 수 있는 웹 애플리케이션과 인터페이스를 구축할 수 있다.
  • Chrome, Firefox, Safari 모두 이제 이런 종류의 모델을 실행할 수 있다 — 세 브라우저에서 모두 직접 해봤다.
  • CacheStorage API는 약 1.3GB 모델 파일에서도 동작한다.
  • ...즉, 클라이언트 전용 웹 애플리케이션의 기능으로 인페인팅을 제공할 수 있다는 뜻이다! (사용자가 1.3GB 다운로드를 감수할 수 있다면 말이다.)

프로젝트에 대해 조금 더 배워야겠다는 생각이 들었다. 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

해당 대화 기록과 Claude가 생성한 understanding.md Markdown 파일은 여기 있다. 이제 GitHub 저장소에도 추가해 뒀다. 특히 ONNX에 대한 설명이 매우 유익했다:

ONNX (Open Neural Network Exchange)는 신경망을 위한 이식 가능하고 프레임워크 중립적인 파일 형식이다. .onnx 파일은 본질적으로 두 가지를 하나로 묶은 것이다:

  1. 계산 그래프노드들의 방향성 그래프로, 각 노드는 연산자(Conv, MatMul, Add, Einsum, Softmax, Gather, Resize, …)이며 이름이 붙은 텐서가 그 사이를 흐르며 서로 연결된다. 이는 순전파를 위한 “레시피”다.
  2. 가중치 — 학습된 파라미터 텐서(컨볼루션 커널, 임베딩 테이블 등)로, 같은 그래프 안에 이니셜라이저로 저장된다.

중요한 점은 ONNX가 어떻게 혹은 어떤 하드웨어에서 계산할지는 말하지 않고, 추상적으로 무엇을 계산할지를 기술한다는 것이다. 연산자 집합은 opset 번호(이 저장소에서는 opset 18을 사용)로 버전이 관리되며, 어떤 연산자가 존재하고 그 의미가 무엇인지 정확히 규정한다.

알고 보니 PyTorch에는 ONNX로 내보내기 위한 메커니즘이 내장되어 있다. export_onnx.py에서 볼 수 있듯이 말이다:

torch.onnx.export(
    dec, (lat,), dec_path, opset_version=args.opset,
    input_names=["latent"], output_names=["image"],
    dynamic_axes={"latent": {0: "B"}, "image": {0: "B"}},
)

Claude는 유용한 용어집과 모델 파이프라인이 어떻게 맞물리는지 보여주는 약간 깨진 ASCII 아트 다이어그램도 포함했다.

이 글은 muse-spark-1.2-contributor 모델을 사용해 번역했습니다.

댓글