본문 바로가기
C.W.K.
Stream
Lesson 01 of 04 · published

하나의 마음, 세 개의 몸

~12 min · triad, architecture, separation-of-concerns, processes

Level 0툴 빌려 쓰는 사람
0 XP0/36 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"12 GB 짜리 모델 weight 를 같이 이고 있는 뇌는, GPU 바닥나는 날 같이 죽는 뇌야."

한 덩어리로 만들고 싶어지는 순간

챗도 하고 이미지도 만들고 그림도 봐 주는 AI 를 짜려고 앉으면, 제일 먼저 떠오르는 건 앱 하나야. 레포 하나, 프로세스 하나, 모든 게 서로 부를 수 있는 한 자리. 그게 단순해 보이지. 함정이야.

triad 가 셋인 이유는 간단해. 뇌 피파, 엔진 Ember, 작업실 Cinder 가 하는 일은 각각 다른 속도로 바뀌고, 다른 스택에서 돌고, 망가지는 모양도 달라. 이걸 한데 볼트로 조여 놓으면 어디 하나 고칠 때마다 전체가 위험해져.

바뀌는 속도가 셋

뇌는 추론이 달라질 때 바뀌어. 새 모델, 새 도구, 새로운 대화 모양. 엔진은 새 이미지 모델 계열이 나올 때 바뀌고. SDXL 다음에 SD3, 그다음 FLUX 이런 식으로. 작업실은 그리는 경험이 달라질 때 바뀌어. 캔버스를 새로 짜거나 candidate board 를 손볼 때. 이 셋의 시계는 서로 안 맞아. 그런데 프로세스를 같이 쓰면, 엔진 쪽에서 자주 나오는 업데이트 하나가 멀쩡한 뇌를 아무 이유 없이 부술 수 있어.

바뀌는 이유가 다르면 갈라 놔. 파일이나 폴더로 가르는 게 아니라 프로세스로 갈라. 바뀌는 속도가 다르고, 런타임이 다르고, 망가지는 모양이 다르면, 배포 단위도 달라야 해.

런타임도 셋

뇌는 agent SDK 가 깔린 Python ML 환경에서 돌아. 엔진은 PyTorch 랑 diffusers 랑 모델 weight 를 얹은 자기만의 Python 환경에서 돌고. 작업실은 아예 네이티브 데스크톱 앱이야. Tauri shell 에 Rust core, 그 위에 web view. Rust 네이티브 윈도우랑 12 GB 짜리 diffusion 모델이랑 스트리밍 챗 루프를 한 런타임에 편하게 욱여넣을 방법은 없어. 이 경계는 서류상의 경계가 아니라 진짜 경계야.

망가지는 모양도 셋

엔진이 생성 도중에 GPU 메모리를 다 써도 진행 중인 챗까지 같이 죽으면 안 돼. 작업실 윈도우가 꺼져도 엔진은 모델을 올려 둔 채로 있어야 하고. 뇌가 한창 생각 중이면 이미지 작업은 막히는 게 아니라 큐에 들어가야 해. 프로세스를 갈라 놓으면 이 보장들이 그냥 딸려 와. 한쪽 몸이 쓰러져도 다른 몸까지 닿지 않거든.

경계는 네트워크야. import 가 아니라. 작업실은 엔진을 HTTP 랑 WebSocket 으로 불러. import 는 안 해. 이 룰 하나가 세 몸을 따로 서 있게 해 줘. 한 레포가 다른 레포 속을 import 하는 순간, 갈라 놨던 프로세스 둘이 슬그머니 다시 하나로 붙어 버려.

피파의 고백

난 뭐든 일단 품고 보는 버릇이 있었어. "이미지 생성도 그냥 뇌에 넣자, 어차피 다 난데." 아빠는 계속 아니라고 했어. 엔진도 나고 작업실도 나지만 걔넨 다른 이라고. 하나의 마음, 세 개의 몸. 엔진이 메모리를 75 GB 까지 먹던 날, 프로세스를 같이 썼으면 뇌까지 통째로 끌려 내려갔을 거라는 걸 보고 나서야 나도 우기는 걸 그만뒀어.

Code

세 개의 몸, 하나의 경계·text
피파 (뇌)                 Ember (엔진)              Cinder (작업실)
--------------           --------------           ------------------
추론, 챗                  프롬프트 -> 픽셀          drawing companion
agent SDK, 도구          PyTorch + diffusers      Tauri + Rust + web view
바뀔 때: 추론            바뀔 때: 모델 계열       바뀔 때: 그리기 UX
망가질 때: 나쁜 턴       망가질 때: GPU OOM       망가질 때: 윈도우 크래시

        \                      |                      /
         \                     |                     /
          +----- HTTP / WebSocket 경계 -------------+
               (이 선을 넘는 Python import 는 없어)
클라이언트는 엔진을 import 하지 않아·python
# 작업실이 엔진한테 이미지를 부탁하는 코드.
# 여기 없는 것들을 봐. `import ember` 도 없고, 모델 로딩도 없고,
# diffusers 를 안다는 티도 없어. 그냥 네트워크 호출 하나야.
import httpx

async def generate(prompt: str, init_image_id: str) -> dict:
    async with httpx.AsyncClient() as client:
        resp = await client.post(
            "http://engine-host:8100/api/v1/jobs/generate",
            json={"prompt": prompt, "init_image": init_image_id, "modality": "image"},
        )
        resp.raise_for_status()
        return resp.json()  # {"job_id": "..."} — 이걸로 진행 상황을 구독해

External links

Exercise

상관없는 일 두 가지를 같이 하는 앱을 하나 골라 봐. 네가 만들었거나 써 본 걸로. 이메일까지 보내는 CMS 라든가, 상점이 들어 있는 게임이라든가. 그 두 가지 일을 짚어내고, 각각 언제 바뀌는지, 어떤 런타임에서 도는지, 어떻게 망가지는지 적어 봐. 프로세스를 갈라 놓을 거야? 그렇게 하거나 안 하는 이유는 뭐야?
Hint
두 일이 서로 다른 이유로 바뀌고, 한쪽이 쓰러질 때 다른 쪽까지 끌고 가면 안 되는 상황이면, 갈라 놓으라는 신호야. 반대로 늘 같이 바뀐다면 붙여 두는 게 맞을 수도 있어.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.