15장 — 그림으로 조건 걸기: 참조 이미지 여러 장

IP-Adapter: 참조 그림을 따로 읽는 크로스 어텐션

ControlNet 은 먹지처럼 칸을 맞춰 더한다. 뼈대 그림이 왼쪽에 있으면 사람도 왼쪽에 그려진다. 그런데 「이 사진 속 사람의 얼굴로, 바닷가에서 뛰는 모습을」이라는 부탁에서 참조 사진의 얼굴은 만들 그림의 어느 칸에 와도 된다. 크기도 자세도 다르다. 칸을 맞춰 더하는 방법으로는 이것을 전할 수 없다. 내용으로 골라 읽는 장치가 필요하다. 마침 U-Net 에는 그런 장치가 이미 있다. 그림 칸들이 글 토큰을 골라 읽는 크로스 어텐션이다. 참조 그림을 토큰 몇 개로 바꿔 거기에 끼워 넣으면 될까?

역사: 이어 붙이자 묻혔다

2023년 텐센트 AI 랩의 예후(Hu Ye)와 동료들도 그 생각에서 출발했다. 그전의 방법들은 CLIP 그림 인코더의 벡터를 조건으로 모델 전체를 다시 학습하거나(비용이 컸다), 그림 특징을 글 특징 줄 끝에 이어 붙여 원래의 크로스 어텐션에 그대로 넣는 작은 어댑터를 붙였다. 논문은 이어 붙이는 방법을 「충분히 효과적이지 않았다」고 적었다. 같은 조건에서 20만 걸음씩 학습해 견주어 보니, 이어 붙인 어댑터는 참조 그림을 덜 닮은 그림을 냈다.

그들이 바꾼 것은 한 가지다. 원래의 크로스 어텐션 층마다 그림만 읽는 크로스 어텐션을 하나씩 곁에 두고, 두 결과를 더했다. 질의는 같은 것을 쓰고 열쇠와 값을 만드는 행렬만 새로 두었다. 새 행렬은 원래 행렬의 값에서 출발했다. 참조 그림은 CLIP 그림 인코더(OpenCLIP ViT-H/14)의 벡터 하나를 선형 층과 층 정규화로 토큰 4개로 편다. 이렇게 Stable Diffusion 1.5 에 붙인 어댑터는 매개변수가 약 2,200만 개였고, 글–그림 쌍 1,000만 개로 V100 GPU 8장에서 100만 걸음 학습했다. 논문은 이 작은 어댑터가 모델 전체를 다시 학습한 그림 조건 모델과 견줄 만하거나 더 낫다고 보고했다.

질의는 하나, 읽는 곳은 둘

그림 칸의 특징 줄에서 질의 Q를 만든다. 글 토큰 줄 c에서는 원래 행렬로 열쇠와 값을, 참조 그림의 토큰 줄 cimg에서는 새 행렬로 열쇠와 값을 만든다.

softmax ⁣(QK⊤dk)V  +  λ softmax ⁣(QK′⊤dk)V′,K′=cimgWK′,V′=cimgWV′\mathrm{softmax}\!\left(\frac{\textcolor{#0078e1}{Q}\textcolor{#c45a00}{K}^{\top}}{\sqrt{\textcolor{#3c4bff}{d_k}}}\right)\textcolor{#2ca02c}{V} \;+\; \textcolor{#87965a}{\lambda}\,\mathrm{softmax}\!\left(\frac{\textcolor{#0078e1}{Q}\textcolor{#c45a00}{K'}^{\top}}{\sqrt{\textcolor{#3c4bff}{d_k}}}\right)\textcolor{#2ca02c}{V'}, \qquad \textcolor{#c45a00}{K'} = \textcolor{#0093b8}{c_{\mathrm{img}}}\textcolor{#665522}{W'_K},\quad \textcolor{#2ca02c}{V'} = \textcolor{#0093b8}{c_{\mathrm{img}}}\textcolor{#665522}{W'_V}
Q질의: 그림 칸마다 하나 (원래 행렬로 만든다)K,V글 토큰 줄 c 에서 원래 행렬로 만든 열쇠와 값cimg참조 그림의 토큰 줄 (CLIP 그림 벡터를 편 4개)K′,V′참조 토큰에서 새 행렬로 만든 열쇠와 값WK′,WV′새로 학습하는 행렬 (원래 행렬 값에서 출발)dk질의⋅열쇠 한 개의 길이λ람다: 참조를 읽은 몫에 곱하는 세기 (0이면 원래 모델)\begin{array}{ll} \textcolor{#0078e1}{Q} & \text{질의: 그림 칸마다 하나 (원래 행렬로 만든다)} \\ \textcolor{#c45a00}{K}, \textcolor{#2ca02c}{V} & \text{글 토큰 줄 c 에서 원래 행렬로 만든 열쇠와 값} \\ \textcolor{#0093b8}{c_{\mathrm{img}}} & \text{참조 그림의 토큰 줄 (CLIP 그림 벡터를 편 4개)} \\ \textcolor{#c45a00}{K'}, \textcolor{#2ca02c}{V'} & \text{참조 토큰에서 새 행렬로 만든 열쇠와 값} \\ \textcolor{#665522}{W'_K}, \textcolor{#665522}{W'_V} & \text{새로 학습하는 행렬 (원래 행렬 값에서 출발)} \\ \textcolor{#3c4bff}{d_k} & \text{질의·열쇠 한 개의 길이} \\ \textcolor{#87965a}{\lambda} & \text{람다: 참조를 읽은 몫에 곱하는 세기 (0이면 원래 모델)} \end{array}
IP-Adapter 의 따로 읽는 크로스 어텐션. 같은 질의가 글 토큰 77개와 참조 토큰 4개를 따로 읽고, 참조를 읽은 몫에 λ 를 곱해 더한다. 원래 행렬은 얼리고 K′·V′ 만 새로 학습한다. 스크립트 sources/ch15/figs.py
IP-Adapter 의 따로 읽는 크로스 어텐션. 같은 질의가 글 토큰 77개와 참조 토큰 4개를 따로 읽고, 참조를 읽은 몫에 λ 를 곱해 더한다. 원래 행렬은 얼리고 K′·V′ 만 새로 학습한다. 스크립트 sources/ch15/figs.py

새로 학습하는 것은 크로스 어텐션 층 16개마다의 W′K, W′V와 그림 벡터를 토큰 넷으로 펴는 층뿐이다. ComfyUI 의 Stable Diffusion 1.x U-Net 에서 크로스 어텐션의 행렬 모양을 읽어 세면 새 열쇠·값 행렬이 1,917만 개, 펴는 층이 315만 개, 합해 2,232만 개로 논문의 「약 22M」과 맞는다. λ는 생성할 때 고르는 손잡이다. 학습 때는 글과 그림을 각각 5%, 둘 다 5% 확률로 지워, 분류기 없는 가이던스를 쓸 수 있게 했다.

이렇게 참조 그림을 토큰 몇 개로 바꿔 원래 크로스 어텐션 곁의 새 크로스 어텐션으로 따로 읽히게 하는 어댑터를 IP-Adapter (그림 프롬프트 어댑터 / Image Prompt Adapter)라 한다.

ML에서: 토큰 4개, 16개, 그리고 여러 장

토큰 4개는 CLIP 그림 벡터 하나를 편 것이라 그림의 대략적인 내용과 화풍을 담는다. 논문은 CLIP 그림 인코더의 끝에서 둘째 층에서 칸마다의 특징을 꺼내, 학습하는 질의 토큰 16개가 골라 읽게 한 판도 실었다. 참조를 더 닮게 그리지만 구도까지 따라 그려 다양성이 준다고 적었다. ComfyUI 에서 많이 쓰는 확장 노드(comfyui_ipadapter_plus)는 모델 파일을 보고 토큰 수를 4개, Plus 판은 16개로 정한다. 참조 그림을 여러 장 넣으면 그림마다 만든 토큰을 이어 붙이거나(concat, 4장이면 16개), 더하거나, 평균 낸다(combine_embeds). 같은 「그림을 토큰 몇 개로 바꿔 글 곁에 둔다」는 생각은 디퓨전 트랜스포머에서도 쓰였다. FLUX.1 의 Redux 는 SigLIP 그림 인코더의 토큰 줄을 선형 층 둘로 글 토큰과 같은 폭(4096)으로 옮겨 T5 글 토큰 줄 끝에 이어 붙인다(comfy/ldm/flux/redux.py, nodes.py 의 StyleModelApply). 합동 어텐션 모델에서는 글 토큰 줄에 붙이는 것이 곧 그림 토큰과 한 줄에 서는 것이다.

문제 4. 두 방송의 볼륨

차 안에서 뉴스 방송과 음악 방송을 함께 틀고 싶다. 방법 ㉠ 은 두 방송을 한 안테나로 받아 섞인 소리 하나만 스피커로 보낸다. 섞인 소리에서 뉴스는 세기 1, 음악은 세기 0.4다. 방법 ㉡ 은 두 방송을 따로 받아, 음악 쪽에만 볼륨 손잡이 λ 를 달고 더한다. (가) ㉡ 에서 λ = 2, 0.5, 0 이면 음악의 세기는? (나) ㉠ 에서 음악만 두 배로 키울 수 있는가? (다) ㉠ 에서 전체 볼륨을 두 배로 올리면?

김민준 M01
김민준

(가)는 0.8, 0.2, 0이요. 0이면 뉴스만 나오는 원래 라디오고요.

이서연 S01
이서연

(나)는 못 해. 섞인 소리 안에서 어느 몫이 음악인지 가를 손잡이가 없어.

선생님 T02
선생님

(다)를 해 보면요?

김민준 M05
김민준

뉴스 2, 음악 0.8이요. 음악을 키우려다 뉴스까지 커져요. 비율은 그대로고요.

선생님 T13
선생님

그래요. ㉡ 이 IP-Adapter 의 따로 읽기예요. 글을 읽은 몫과 참조를 읽은 몫이 따로 나오니까, 참조 쪽에만 λ 를 곱할 수 있어요.

이서연 S11
이서연

그리고 λ = 0이면 어댑터를 떼어 낸 원래 모델이 되는 거고.

김민준 M07
김민준

조별 발표 녹화를 한 마이크로 받으면 나중에 한 사람 목소리만 키울 수 없는 거랑 같네요.

문제 5. 하나의 softmax 에 이어 붙이면

그림 칸 하나의 질의가 글 토큰 77개와 참조 토큰 4개를 읽는다. (가) 둘을 이어 붙여 softmax 하나로 읽고, 81개의 점수가 모두 같다면 참조 토큰 4개가 받는 비중의 합은? (나) 참조 토큰의 점수가 글 토큰보다 모두 2만큼 높다면? (다) 글 토큰이 512개인 모델에서 (나)를 다시 계산하라. (라) 따로 읽으면 참조 쪽 비중의 합은 얼마인가?

김민준 M01
김민준

(가)는 4 ÷ 81 = 0.049, 5% 쯤이요.

이서연 S01
이서연

(나)는 참조 하나의 무게가 e² = 7.39배니까 4 × 7.39 ÷ (77 + 4 × 7.39) = 0.277.

선생님 T02
선생님

참조 쪽이 더 높은 점수를 받았는데도 30%가 안 되네요. (다)는요?

김민준 M06
김민준

4 × 7.39 ÷ (512 + 29.56) = 0.055요. 같은 참조, 같은 점수인데 글 토큰이 많아지니까 5% 남짓으로 줄었어요.

이서연 S08
이서연

하나의 softmax 에서는 비중의 합이 1로 묶여 있어서, 참조 토큰 넷이 글 토큰 수십, 수백 개와 그 1을 나눠 가져. 글이 길수록 참조는 묻혀.

선생님 T02
선생님

그럼 (라)는요?

이서연 S11
이서연

참조만 읽는 softmax 에서는 참조 토큰 넷의 비중 합이 늘 1이에요. 글 토큰이 몇 개든 상관없고, 세기는 λ 가 따로 정해요.

선생님 T13
선생님

논문은 이어 붙이기가 「충분히 효과적이지 않았다」는 결과만 적었어요. 이 계산은 그렇게 될 만한 까닭 하나를 보여 주는 거예요.

김민준 M08
김민준

단톡방 공지 하나가 잡담 백 개 사이에 묻히는 거랑, 공지방을 따로 두는 거랑 차이네요.

문제 6. 두 장을 이어 붙일까, 평균 낼까

강아지 사진 A 와 수채화 B 를 참조로 넣는다. 두 그림의 CLIP 그림 벡터는 길이 1이고 서로 수직이라고 하자. (가) 이어 붙이기(concat)와 평균 내기로 만든 참조 토큰은 각각 몇 개인가? (나) 평균 낸 그림 벡터의 길이와, A·B 와의 코사인 유사도는? (다) 이어 붙인 참조 토큰 줄에서 A 의 토큰들과 B 의 토큰들의 순서를 맞바꾸면 따로 읽는 크로스 어텐션의 출력은 어떻게 되는가? (라) 「A 의 강아지를 B 의 화풍으로」와 「B 의 화풍을 띤 무언가를 A 의 색으로」를 IP-Adapter 의 참조 쪽만으로 가를 수 있는가?

김민준 M01
김민준

(가)는 이어 붙이면 4 + 4 = 8개, 평균 내면 4개요. (나)는 (A + B)/2 의 길이가 √0.5 = 0.707, 코사인도 0.5 ÷ 0.707 = 0.707이에요.

이서연 S03
이서연

평균 벡터는 어느 쪽과도 45° 떨어져 있네. 강아지 사진도 수채화도 아닌 그 사이 어딘가의 그림 벡터야.

선생님 T02
선생님

(다)는 어때요? 어텐션 식에서 열쇠 줄의 순서를 바꾸면 무엇이 바뀌죠?

김민준 M04
김민준

점수 줄의 순서가 바뀌니까… 출력도 바뀌지 않아요?

이서연 S08
이서연

점수와 값이 같은 순서로 함께 바뀌어. softmax 를 해서 값에 곱해 더하는 건 순서와 상관없는 합이라, 출력은 하나도 안 바뀌어. 참조 토큰에 위치 정보가 없으니 어텐션에게 이 줄은 순서 없는 주머니야.

김민준 M05
김민준

그럼 (라)는 못 가르네요. A 와 B 가 바뀌어도 똑같이 읽히는데, 무엇이 A 의 몫인지 알 수가 없어요. 「A 의 강아지」라는 말은 글 쪽에 있는데, 글 쪽은 A 라는 그림을 모르고요.

선생님 T13
선생님

그래요. 여러 장을 넣는 것까지는 되지만, 참조마다 이름표를 붙이고 글에서 그 이름표를 부르는 길은 아직 없어요.

김민준 M07
김민준

과제 자료를 이름 안 쓰고 한 상자에 몰아 넣으면, 조교가 내용은 다 봐도 누가 어느 걸 냈는지는 모르는 거랑 같네요.