VAE — 모든 작업이 벌어지는 압축된 latent 공간이랑 네가 실제로 보는 픽셀 사이를 옮겨 줘.
왜 하나가 아니라 다섯이냐면
박스마다 모델 계열이 바뀔 때 따로따로 달라지거든. SDXL 이랑 FLUX 는 VAE 라는 개념은 같이 쓰지만 실제 VAE weight 는 달라. backbone 은 아예 다른 걸 쓰고. sampler 는 어떤 건 같이 쓰고 어떤 건 안 그래. 이 다섯 작업이 함수 하나에 다 들어가 있으면, 모델 계열 하나가 들어올 때마다 그 함수 안에서 다른 계열들이랑 다 부딪혀. 박스 다섯으로 나눠 두면 각 계열이 필요한 박스만 새로 내놓고 나머지는 그냥 재사용해.
변화가 갈라지는 선을 따라 나눠. 옳은 module 경계는 아무 데나 그은 게 아니야. 모델 계열이 달라질 때 실제로 갈라지는 자리에 정확히 있어. text encoder 가 바뀌니까 text encoder 가 박스인 거고, backbone 이 제일 많이 바뀌니까 backbone 이 제일 중요한 박스인 거야. 경계는 변화의 단층선을 따라가.
진짜 제품은 인터페이스야
박스 자체보다 그 사이의 seam 이 더 중요해. sampler 는 backbone 이 U-Net 인지 DiT 인지 신경 안 써. "노이즈 낀 latent 랑 timestep 을 줄 테니 노이즈를 예측해" 면 충분하거든. 그 약속이 인터페이스야. 모든 backbone 이 그걸 지키는 한 sampler 는 영영 안 바뀌어. 박스를 갈아 끼울 수 있게 만드는 건 인터페이스고, 박스는 그 뒤에 있는 구현일 뿐이야.
구현보다 seam 을 먼저 설계해. 어렵고 값진 작업은 한 박스가 다른 박스한테 뭘 약속하는지 정하는 거야. 메서드 시그니처, tensor shape, 약속의 내용. seam 이 맞으면 여러 구현이 그 뒤에 꽂혀. seam 이 틀리면 구현을 아무리 잘 짜도 소용없고.
recipe 가 박스를 지나가는 길
생성 요청이 recipe 로 들어와서 박스를 순서대로 통과해. text encoder 가 프롬프트를 conditioning 으로 바꾸고, conditioning-attach 층이 LoRA 나 ControlNet 을 얹고, sampler 가 매 step 마다 backbone 을 불러 가며 루프를 돌고, 마지막에 VAE 가 완성된 latent 를 픽셀로 풀어. recipe 는 박스 이름을 한 번도 안 대. 뭘 원하는지만 말하고, 어떤 다섯 개를 쓸지는 엔진이 정해.
아빠가 이 다섯 박스를 처음 그려 줬을 때, 난 이미 diffusion 이 어떻게 도는지 오래전부터 '알고' 있었어. 근데 하나의 흐릿한 과정으로 알고 있었던 거야. 그걸 이름 붙은 약속을 주고받는 다섯 번의 인계로 보게 된 순간, 그게 더 이상 검은 상자가 아니게 됐어. 내가 따져 보고 고치고 늘릴 수 있는 시스템이 된 거지. seam 에 이름을 붙이는 게 아는 걸 이해로 바꿔 놨어.
Code
protocol 다섯, 구현은 여럿·python
# 다섯 박스를 protocol(인터페이스)로 적으면 이래. 구현은 모델 계열마다
# 다르지만 이 약속은 안 달라져.
from typing import Protocol
import torch
class TextEncoder(Protocol):
def encode(self, prompt: str) -> torch.Tensor: ... # 박스 1
class Backbone(Protocol):
def predict_noise(self, latents: torch.Tensor,
t: int, cond: torch.Tensor) -> torch.Tensor: ... # 박스 2
class Sampler(Protocol):
def sample(self, backbone: Backbone, cond: torch.Tensor,
steps: int) -> torch.Tensor: ... # 박스 3
class Conditioning(Protocol):
def attach(self, backbone: Backbone) -> Backbone: ... # 박스 4 (LoRA/ControlNet)
class VAE(Protocol):
def decode(self, latents: torch.Tensor) -> torch.Tensor: ... # 박스 5
# SDXL 이 한 벌을 내놓고 FLUX 가 또 한 벌을 내놔.
# 위 protocol 은 안 바뀌어. 그게 갈아 끼울 수 있게 만드는 거야.
recipe 는 무엇을, 박스는 어떻게를·python
# recipe 가 박스 다섯을 순서대로 지나가.
def run(recipe, te: TextEncoder, bb: Backbone,
sampler: Sampler, cond: Conditioning, vae: VAE):
conditioning = te.encode(recipe.prompt) # 박스 1: 텍스트 -> 벡터
bb = cond.attach(bb) # 박스 4: LoRA/ControlNet 얹기
latents = sampler.sample(bb, conditioning, # 박스 3 이 루프로 박스 2 를 굴려
steps=recipe.steps)
image = vae.decode(latents) # 박스 5: latent -> 픽셀
return image
# recipe 는 박스 이름을 하나도 안 댔어. 엮은 건 엔진이야.
네가 이해하고 있는 다단계 시스템을 하나 골라 봐. 빌드 파이프라인이든, 요청/응답 사이클이든, 게임 루프든. 별개의 박스로 쪼개. 박스 둘 사이 seam 마다 약속을 한 줄로 적어. 앞 박스가 뒤 박스한테 뭘 약속해? 그 약속들이 얼마나 또렷한지가 곧 네 아키텍처의 건강 상태야.
Hint
seam 의 약속을 한 문장으로 못 적겠으면 두 박스가 아직 붙어 있는 걸 거야. 깨끗한 seam 은 '경우에 따라' 를 안 붙이고도 적을 수 있는 약속을 갖고 있어.
Progress
Progress is local-only — sign in to sync across devices.