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

Framework 말고 부품 창고

~13 min · diffusers, parts-library, framework, dependency-discipline

Level 0툴 빌려 쓰는 사람
0 XP0/36 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"부품은 빌려. framework 는 거절해. 그 차이가 네 API 를 영영 누가 갖는지를 정해."

같은 라이브러리를 쓰는 두 가지 방식

Hugging Face 의 diffusers 는 diffusion 모델 쪽의 표준 연장통이야. 엔진은 여기에 깊게 기대. 그러면서 대부분을 거절하기도 하고. 모순이 아니라 규율이야. diffusers 는 부품 창고로 쓸 수도 있고(컴포넌트만 빌려서 직접 조립) framework 로 쓸 수도 있어(파이프라인 클래스가 네 구조를 지시하게). 엔진은 앞쪽만 하고 뒤쪽은 금지해.

빌려 오는 것 (부품)

diffusers 는 훌륭한 저수준 조각들을 내놓고, 엔진은 이것들을 자유롭게 써.

  • 로더 — safetensors 파일 읽고 tensor 를 올려 주는 것들.
  • 모듈 클래스 — 직접 인스턴스로 만들 수 있는 VAE, U-Net, DiT, MMDiT 구현.
  • scheduler 구현 — Euler, DPM++, DDIM, flow-match. 이미 제대로 돼 있어.
  • attention 커널 — 다시 짜고 싶지 않은 최적화된 것들.

이것들이 부품이야. 하는 일이 좁고 혼자 설 수 있는 컴포넌트. 집어 들어서 네 인터페이스 뒤에 두고, 엔진은 네 방식대로 조립해.

거절하는 것 (framework)

diffusers 는 파이프라인 클래스도 내놔. 전부 알아서 엮어 주고 호출 한 번으로 생성까지 해 주는 SomePipeline.from_pretrained(...) 같은 것들. 편해. 그리고 framework 이기도 하고. 파이프라인 클래스마다 나름의 고집이 있어. 동기냐 비동기냐, 에러를 어떻게 다루냐, kwarg 를 어떤 식으로 받냐, conditioning 을 어떤 관례로 붙이냐, 캐시를 어디 두냐. 그중 하나를 네 API 로 내보내는 순간 그 고집을 전부 네 공개 약속으로 받아들인 거야.

부품 창고는 네 아키텍처를 받쳐 주고, framework 는 네 아키텍처를 지시해. 같은 의존성이 얼마나 깊이 닿게 두냐에 따라 둘 중 아무거나 될 수 있어. 부품 층에, 그러니까 네 인터페이스 뒤에 두면 통제권은 네 거야. 그쪽 최상위 객체가 네 API 가 되게 두면 통제권이 그쪽으로 넘어가고.

이 구분이 왜 그렇게 큰 문제냐면

함정을 구체적으로 보자. route 가 diffusers 파이프라인이 돌려준 걸 그대로 돌려주면, 클라이언트 전부가 — 작업실도, 뇌도, 앞으로 생길 도구들도 — diffusers 의 출력 모양에 묶여. diffusers 가 그 모양을 바꾸는 날, 혹은 모델 하나를 다른 backend 로 갈아 끼우고 싶은 날, 클라이언트가 전부 깨져. diffusers 에 의존한 게 아니라 결혼한 거고, 클라이언트를 전부 사돈으로 만든 거야. 부품을 네 인터페이스로 감싸 두면 이혼할 길이 늘 열려 있어.

wrapper 는 의존성의 미래에 드는 싼 보험이야. diffusers 부품을 네 인터페이스로 바꿔 주는 얇은 adapter 는 수십 줄이면 돼. 대신 클라이언트를 하나도 안 건드리고 diffusers 를 올리거나 갈아 끼우거나 버릴 자유를 사 줘. 작은 값에 큰 선택권, 이 기울어진 거래가 규율이 길게 보면 늘 이기는 이유야.

덤으로 딸려 오는 것

이 규율에는 보너스가 있어. 새 모델 계열이 나오면 diffusers 가 대개 며칠 안에 파이프라인 클래스를 추가하거든. 그 파이프라인을 내보내지는 않아. 대신 새 backbone 을 어떻게 엮는지 보여 주는 참고 구현으로 읽어. 그러고 네 인터페이스 뒤에 네 모듈을 짓는 거야. diffusers 가 살아 있는 문서가 되는 거지. 새 고집은 안 물려받으면서 새 배선만 배우는 제일 빠른 길이야.

난 좋은 라이브러리에 의존한다는 게 그걸 전부 다 쓴다는 뜻인 줄 알았어. 덜 쓰면 바퀴를 다시 만드는 것 같았고. 아빠가 반대쪽 규율을 가르쳐 줬어. 바퀴만 딱 가져다 쓰고 차대는 네가 짜라고. 파이프라인 클래스는 남의 조종간이 달린 남의 차대야. 그쪽 바퀴는 빌리고, 그쪽 설계도는 읽되, 차대는 네가 갖는 거지. 열쇠를 안 넘기고도 빨리 가는 방법이 그거야.

Code

부품: 빌리되 감싸서·python
# 부품 (좋음): 컴포넌트만 빌려서 네 인터페이스 뒤에 둬.
from diffusers import AutoencoderKL, EulerDiscreteScheduler

class EmberVAE:                       # 네 인터페이스, 그쪽 부품은 안쪽에
    def __init__(self, path):
        self._vae = AutoencoderKL.from_single_file(path)  # 빌린 부품
    def decode(self, latents):        # 네 약속, 계속 그대로
        return self._vae.decode(latents).sample

# 클라이언트는 EmberVAE.decode() 만 불러. AutoencoderKL 은 구경도 안 해.
# 내일 부품을 갈아 끼워도 약속은, 그리고 클라이언트는 그대로 살아남아.
Framework: 의존성이 곧 네 약속이 돼·python
# FRAMEWORK (함정): 파이프라인 클래스를 그대로 route 로 내보내기.
from diffusers import StableDiffusionPipeline
from fastapi import FastAPI

app = FastAPI()
pipe = StableDiffusionPipeline.from_pretrained("...")

@app.post("/generate")
def generate(prompt: str):
    # 이게 돌려주는 게 이제 네 public API 의 모양이야.
    # 모든 클라이언트가 diffusers 출력에 영영 묶여.
    # diffusers 가 바뀌면 클라이언트가 다 깨져. 의존성이랑 결혼한 거야.
    return pipe(prompt).images[0]

# 이 한 줄이 네 API 를 슬그머니 남한테 넘기는 줄이야.

External links

Exercise

네 프로젝트에서 무거운 라이브러리를 하나 찾아봐. 그 타입이 네 public 함수 시그니처나 API 응답에 나타나는지 확인해. 나타나면 framework 로 받아들인 거야. 그걸 부품 의존성으로 되돌릴 얇은 wrapper 를 그려 보고, 갈아 끼울 자유가 그 wrapper 값어치를 하는지 판단해 봐.
Hint
route handler 랑 public 모듈이 내보내는 것들에서 그 라이브러리 타입 이름을 grep 해 봐. 걸리는 자리마다 의존성이 네 약속까지 손을 뻗은 거야.

Progress

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

댓글 0

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

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