14장 — 글을 읽는 인코더: CLIP의 마지막 층에서 언어모델의 여러 층으로

조건이 들어가는 자리: 글 벡터가 디퓨전 신경망을 만나는 길

글 인코더가 토큰마다 벡터 줄을 넘겼다. Stable Diffusion 1.x 라면 77 × 768, Krea 2 라면 토큰 수 × 30,720이다. 이 줄은 그림을 만드는 신경망의 어디로 들어갈까? 같은 글 벡터라도 들어가는 자리에 따라 그림 쪽이 글을 읽는 방식이 달라진다. 어텐션과 시간 조건을 넣는 법을 한 번씩 되짚으며 모델마다 견줘 보자.

세 길

첫째 길은 크로스 어텐션이다. 그림 쪽 토큰이 질의를 내고, 글 토큰 줄이 열쇠와 값을 낸다.

Attn(h,c)=softmax ⁣(QK⊤dk)V,Q=h WQ,K=c WK,V=c WV\mathrm{Attn}(\textcolor{#a5003c}{h}, \textcolor{#0093b8}{c}) = \mathrm{softmax}\!\left(\frac{\textcolor{#0078e1}{Q}\textcolor{#c45a00}{K}^{\top}}{\sqrt{\textcolor{#3c4bff}{d_k}}}\right)\textcolor{#2ca02c}{V}, \qquad \textcolor{#0078e1}{Q} = \textcolor{#a5003c}{h}\,\textcolor{#665522}{W_Q},\quad \textcolor{#c45a00}{K} = \textcolor{#0093b8}{c}\,\textcolor{#665522}{W_K},\quad \textcolor{#2ca02c}{V} = \textcolor{#0093b8}{c}\,\textcolor{#665522}{W_V}
h그림 쪽 토큰 줄의 은닉 상태 (잠재 그림의 칸이나 조각마다 벡터 하나)c글 쪽 토큰 줄 (글 인코더의 출력)Q질의: 그림 토큰마다 「나는 무엇이 궁금한가」K열쇠: 글 토큰마다 「나는 무엇에 대한 것인가」V값: 글 토큰마다 건네줄 내용dk질의⋅열쇠 한 개의 길이WQ,WK,WV디퓨전 쪽에서 학습하는 행렬 (매개변수)\begin{array}{ll} \textcolor{#a5003c}{h} & \text{그림 쪽 토큰 줄의 은닉 상태 (잠재 그림의 칸이나 조각마다 벡터 하나)} \\ \textcolor{#0093b8}{c} & \text{글 쪽 토큰 줄 (글 인코더의 출력)} \\ \textcolor{#0078e1}{Q} & \text{질의: 그림 토큰마다 「나는 무엇이 궁금한가」} \\ \textcolor{#c45a00}{K} & \text{열쇠: 글 토큰마다 「나는 무엇에 대한 것인가」} \\ \textcolor{#2ca02c}{V} & \text{값: 글 토큰마다 건네줄 내용} \\ \textcolor{#3c4bff}{d_k} & \text{질의·열쇠 한 개의 길이} \\ \textcolor{#665522}{W_Q}, \textcolor{#665522}{W_K}, \textcolor{#665522}{W_V} & \text{디퓨전 쪽에서 학습하는 행렬 (매개변수)} \end{array}

그림 칸마다 글 토큰 가운데 자기에게 맞는 것을 골라 읽는다. 「red」 토큰은 정육면체가 그려질 칸들이 많이 읽고, 「blue」 토큰은 공이 그려질 칸들이 많이 읽기를 바라는 것이다. Stable Diffusion 1.x, 2, SDXL 의 U-Net이 이 길을 쓴다.

둘째 길은 합동 어텐션이다. 그림 토큰 줄과 글 토큰 줄을 한 줄로 이어 붙여 셀프 어텐션 하나에 넣는다. 그림이 글을 읽을 뿐 아니라 글 토큰도 그림 토큰을 읽고 층마다 바뀐다. Stable Diffusion 3 는 두 줄에 서로 다른 가중치를 두고 어텐션에서만 이어 붙이고, FLUX.1 은 앞의 블록 19개만 그렇게 한 뒤 두 줄을 합친다. Krea 2 는 몸통 전체가 가중치 한 벌로 이은 줄을 처리하는 한 줄 블록이다. 글 융합 단계를 지난 글 토큰 줄을 그림 토큰 줄 앞에 이어 붙여 한 줄 블록 28개에 통과시키고, 글 토큰의 위치 좌표는 모두 (0, 0, 0)으로 둔다(SingleStreamDiT.forward).

셋째 길은 벡터 하나를 시간 조건과 함께 넣는 것이다. 잡음 수준(시간)을 벡터로 바꿔 층마다 정규화 뒤의 크기와 치우침을 조절하는 장치(adaLN)에, 문장 벡터 하나를 더해 넣는다. 토큰 줄이 아니므로 「어느 칸이 무엇을 읽나」는 없고, 그림 전체의 분위기를 한꺼번에 민다.

모델 글 인코더와 꺼내는 층 토큰 줄이 들어가는 길 벡터 하나가 들어가는 길
Stable Diffusion 1.x CLIP ViT-L 마지막 층, 77 × 768 크로스 어텐션 없음
SDXL CLIP ViT-L·bigG 끝에서 둘째 층을 이어 77 × 2048 크로스 어텐션 bigG 문장 벡터 1280 + 그림 크기 → 시간 임베딩에 더함
Stable Diffusion 3 CLIP 둘(끝에서 둘째 층) + T5-XXL 합동 어텐션(두 줄, 가중치 따로) CLIP 문장 벡터 둘을 이어 2048 → 시간과 함께 adaLN
FLUX.1 T5-XXL 마지막 층 4096, CLIP ViT-L 문장 벡터 합동 어텐션(앞 블록은 가중치 따로) CLIP 문장 벡터 768 → 시간과 함께
Krea 2 Qwen3-VL-4B 12개 층, 30,720 → 글 융합 → 6144 합동 어텐션(한 줄 블록, 가중치 한 벌) 없음(시간만)

표의 사실은 ComfyUI 의 모델 정의(comfy/supported_models.py, comfy/sdxl_clip.py, comfy/text_encoders/sd3_clip.py·flux.py, comfy/ldm/flux/model.py, comfy/ldm/modules/diffusionmodules/mmdit.py)와 Krea 2 포팅 코드에서 확인했다.

flowchart LR
  T["글"] --> E["글 인코더<br/>층 하나 또는 여러 층"]
  E --> S["토큰 줄 c"]
  E --> P["문장 벡터 하나"]
  S --> X["크로스 어텐션<br/>그림이 글을 읽는다"]
  S --> J["합동 어텐션<br/>한 줄로 이어 서로 읽는다"]
  P --> A["시간 벡터에 더해 adaLN<br/>그림 전체를 한꺼번에 민다"]

ML에서: 왜 벡터 하나의 길은 사라지는가

Stable Diffusion 1.x 에는 없던 문장 벡터 길이 SDXL·SD3·FLUX.1 에서 생겼다가 Krea 2 에서 다시 사라졌다. 문장 벡터는 CLIP 처럼 문장 전체를 한 벡터로 줄이도록 학습한 인코더가 있어야 나온다. 언어모델 인코더에는 그런 자리가 없다. 다음 토큰을 맞히는 모델의 마지막 토큰 은닉 상태는 문장 요약이 아니라 「다음에 올 것」 쪽으로 기울어 있기 때문이다. 대신 합동 어텐션에서 글 토큰 줄 전체가 층마다 그림과 함께 바뀌므로, 문장 전체의 분위기도 그 줄을 통해 전해진다.

문제 12. 세 가지 회의 방식

기획 회의에 디자이너 6명과 고객사 담당자 3명이 들어온다. 질문 하나는 한 사람이 다른 한 사람에게 묻는 것이다(자기에게 묻는 것도 센다). (가) 디자이너만 고객사 담당자에게 묻는 방식이면 질문은 몇 가지인가? (나) 9명이 한 원탁에서 누구나 누구에게나 묻는 방식이면? (다) 사장이 회의실 조명 밝기 하나만 바꿔 분위기를 정하는 방식은 (가)(나)와 무엇이 다른가?

김민준 M01
김민준

(가)는 6 × 3 = 18가지, (나)는 9 × 9 = 81가지요.

이서연 S01
이서연

81에는 디자이너끼리 묻는 36가지, 고객사끼리 묻는 9가지, 서로 묻는 36가지가 다 들어 있네. 고객사가 디자이너에게 묻는 18가지가 (가)에는 없었고.

선생님 T02
선생님

(가)에서 고객사 담당자의 생각은 회의 동안 바뀌나요?

이서연 S08
이서연

안 바뀌어요. 묻기만 받으니까 처음 들고 온 생각 그대로예요. (나)에서는 디자이너 시안을 보고 고객사 쪽 생각도 바뀌고요.

김민준 M07
김민준

(다)는 아무한테도 안 묻고 모두에게 똑같이 영향을 주는 거네요. 조명이 어두우면 다들 차분한 시안을 내고요.

선생님 T13
선생님

그래요. (가)가 크로스 어텐션, (나)가 합동 어텐션, (다)가 adaLN 으로 넣는 벡터 하나예요.

문제 13. 견주는 쌍의 수

1024 × 1024 그림을 VAE가 가로세로 8배 줄이고 2 × 2 패치로 묶으면 그림 토큰은 몇 개인가? 글 토큰이 512개일 때 (가) 크로스 어텐션이 견주는 (질의, 열쇠) 쌍 (나) 그림 토큰끼리의 셀프 어텐션 쌍 (다) 합동 어텐션의 쌍은 각각 몇 개인가? (라) 글 토큰이 77개라면 합동 어텐션은 그림끼리 셀프 어텐션보다 몇 배 무거운가?

김민준 M01
김민준

1024 ÷ 8 ÷ 2 = 64라서 64 × 64 = 4096개요. (가)는 4096 × 512 = 2,097,152, (나)는 4096² = 16,777,216, (다)는 4608² = 21,233,664요.

이서연 S03
이서연

합동이 그림끼리보다 1.27배, 생각보다 크지 않네. 그림끼리 견주는 게 이미 대부분이라서.

선생님 T02
선생님

(라)는요?

김민준 M01
김민준

4173² = 17,413,929라서 1.04배요. 글이 짧으면 합동 어텐션을 해도 거의 공짜예요.

이서연 S11
이서연

크로스 어텐션을 따로 두는 것보다 한 줄로 합치는 쪽이 셈은 조금 더 들지만, 그만큼 글 토큰도 층마다 그림을 보고 바뀌어. 512토큰짜리 긴 프롬프트를 넣는 모델일수록 그 1.27배가 커지는 거고.

김민준 M08
김민준

단톡방에 고객사 세 명을 초대해도 대화량은 별로 안 늘고, 대신 그 사람들도 시안을 보면서 말을 바꿀 수 있는 거네요.

문제 14. 위치가 모두 0인 글 토큰

Krea 2 는 그림 토큰에는 (0, 행, 열) 좌표를 주고 글 토큰에는 모두 (0, 0, 0)을 준다. 어텐션은 위치 정보를 따로 주지 않으면 토큰의 순서를 모른다. (가) 그렇다면 Krea 2 의 디퓨전 쪽은 「a red cube on top of a blue sphere」와 「a blue cube on top of a red sphere」를 가를 수 없는가? (나) 그림 토큰에 좌표를 주는 까닭은 무엇인가?

김민준 M01
김민준

위치가 다 같으면 글 토큰 줄을 뒤섞어도 똑같이 보일 테니까 (가)는 가를 수 없어요. 낱말 주머니랑 같아지는 거죠.

선생님 T02
선생님

디퓨전 쪽에 들어오는 글 토큰 「red」의 벡터는 어디서 왔죠?

김민준 M05
김민준

Qwen 의 은닉 상태요. 아, Qwen 안에서 이미 위치를 넣고 36층을 지나왔으니까, 「cube 앞의 red」라는 게 벡터 안에 들어 있겠네요. 고양이 색 탐침이 1층부터 98%를 넘었던 것처럼요.

이서연 S08
이서연

토큰 줄을 섞어도 각 토큰이 제 앞뒤를 이미 알고 있으니 순서 정보가 사라지지 않아. 디퓨전 쪽이 순서를 다시 알려 줄 필요가 없는 거지.

선생님 T02
선생님

그럼 (나)는요?

이서연 S11
이서연

그림 토큰은 잠재 그림의 칸을 잘라 만든 거라, 어텐션에 들어가기 전에는 자기가 어느 칸인지 아무것도 몰라요. 위치가 없으면 위쪽 칸과 아래쪽 칸을 못 가르니, 「위에 놓인 정육면체」를 그릴 수가 없어요.

김민준 M07
김민준

회의 자료에 쪽 번호가 없어도, 문장마다 「앞에서 말한 대로」가 들어 있으면 순서를 되살릴 수 있는 거네요.