Notice
Recent Posts
Recent Comments
Link
«   2026/06   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
Archives
Today
Total
관리 메뉴

빙글빙글 돌아가는 바람개비

[Study] CFG++ 본문

Image Generation/Study

[Study] CFG++

바람개비은하 2026. 4. 6. 22:43

CFG 다음에 등장한 CFG++

CFG (Classifier-Free Guidance) 는 텍스트-이미지 디퓨전 모델에서 거의 기본처럼 쓰이는 기법이다. 조건부 예측과 무조건부 예측의 차이를 이용해 프롬프트 방향을 더 강하게 반영한다. 그래서 같은 모델이라도 guidance scale 을 높이면 프롬프트를 더 잘 따르는 이미지가 나온다.

근데 CFG 에는 분명한 부작용도 있다. scale 을 높이면 프롬프트 충실도는 올라가지만, 이미지가 과하게 날카로워지거나 색이 과포화되고, 다양한 결과 대신 비슷한 결과만 반복되는 경우가 생긴다. 이미지 편집에서 중요한 DDIM inversion 도 불안정해질 수 있다.

CFG++ 는 이걸 "scale 을 낮추자" 로 해결하는 방법이 아니다. 논문의 핵심은 기존 CFG 가 프롬프트 방향을 강화한 값을 sampling 업데이트에 너무 직접적으로 쓴다는 점을 짚고, 그 사용 위치를 분리하는 데 있다.

참고 논문 — Hyungjin Chung et al., CFG++: Manifold-constrained Classifier Free Guidance for Diffusion Models. 논문 PDF · 프로젝트 페이지


1. Sampler 가 한 step 에서 뭘 하는지

CFG++ 를 이해하려면 CFG 수식보다 먼저, 디퓨전 sampler 가 한 step 에서 무엇을 하는지 봐야 한다. 매 step 의 입력은 아직 노이즈가 남아 있는 이미지 x_t 다. 모델은 이 이미지에 어떤 노이즈가 섞여 있는지 예측하고, 그 예측을 이용해 다음 상태 x_{t-1} 로 이동한다.

xt (noisy)  →  노이즈 예측 ε  →  깨끗한 이미지 추정 x0  →  다음 상태 xt-1

중요한 점은 ε 가 단지 참고값이 아니라는 거다. sampler 는 이 노이즈 예측값으로 x_0 를 추정하고, 다시 그 추정값을 바탕으로 다음 noisy state 를 만든다.

DDIM 계열 업데이트를 단순화해서 보면 이렇다.

xt = αt · x0 + σt · ε

현재 x_t 가 깨끗한 이미지 x_0 와 노이즈 ε 가 섞인 상태라는 뜻이다. 모델이 예측한 ε 를 이용하면 x_0 를 거꾸로 추정할 수 있다.

x0 추정값 = (xt − σt · ε) / αt

그 다음, 같은 x_0 추정값을 바탕으로 노이즈가 조금 덜 낀 다음 상태를 만든다.

xt-1 = αt-1 · x0 추정값 + σt-1 · ε

여기서 끊기지 않고 봐야 할 부분은, sampler 가 노이즈 예측값 ε 를 받아서 x_0 를 추정하고, 다시 다음 step 의 x_{t-1} 를 만든다는 거다. 같은 ε 가 두 자리에서 다 쓰인다. 그래서 CFG 가 만든 ε_cfg 가 sampler 에 들어가면 업데이트 전체에 영향을 준다.


2. CFG 가 두 개의 노이즈 예측을 만드는 이유

CFG 는 같은 x_t 를 두 번 본다. 한 번은 프롬프트를 넣고 보고, 한 번은 프롬프트 없이 본다.

  • 조건부 예측 ε_cond — 프롬프트를 보고 예측한 노이즈. "red car" 같은 조건이 있으면 그걸 고려해서 현재 이미지에 남아 있는 노이즈를 예측한다.
  • 무조건부 예측 ε_uncond — 빈 프롬프트 또는 null condition 을 넣고 예측한 노이즈. 특정 텍스트 조건 없이, 모델이 학습한 일반적인 이미지 분포 기준에서 예측한다.

두 값의 차이 ε_cond − ε_uncond 는 프롬프트가 만들어내는 방향이라고 볼 수 있다. 조건 없이 볼 때와 텍스트를 넣고 볼 때 예측이 달라지는 만큼이, 텍스트 조건이 샘플을 끌고 가는 방향이다.

기존 CFG 는 이 방향을 scale 만큼 키운다.

εcfg = εuncond + scale × (εcond − εuncond)

scale 이 1 보다 크면 ε_cond 쪽으로 단순히 이동하는 정도를 넘어 그 방향을 더 멀리 외삽(extrapolation) 한다. 그래서 프롬프트 반영이 강해진다.

참고로 ε_uncond 를 "프롬프트를 무시한 값" 으로만 보면 헷갈린다. CFG 안에서는 기준점 역할이다. 조건 없이 모델이 보는 일반적인 이미지 흐름이 있고, 프롬프트를 넣었을 때 그 흐름이 얼마나 바뀌는지를 비교하기 위해 필요하다.


3. 기존 CFG 에서 ε_cfg 는 어디에 쓰이나

앞에서 본 CFG 수식은 단지 새로운 값을 계산하는 것으로 끝나지 않는다. CFG 를 켠 sampler 는 매 step 에서 최종 노이즈 예측값으로 ε_cfg 를 쓴다.

xt → εcond, εuncond 계산 → εcfg → sampler 업데이트 → xt-1

즉 기존 CFG 에서는 아래 두 곳에 같은 ε_cfg 가 들어간다.

  1. 깨끗한 이미지 추정x_t 에서 x_0 를 추정할 때 ε_cfg 를 쓴다. 덕분에 프롬프트가 강하게 반영된 x_0 추정값이 나온다.
  2. 다음 noisy state 생성 — 그 x_0 추정값으로 x_{t-1} 를 만들 때도 ε_cfg 를 쓴다. 이 지점에서 과하게 증폭된 guidance 방향이 실제 sampling trajectory 에도 그대로 반영된다.

이제 CFG++ 가 왜 등장하는지 연결된다. CFG++ 는 ε_cfg 로 프롬프트를 반영하는 것 자체가 문제라고 보지 않는다. 문제는 ε_cfg 를 다음 noisy state 를 만드는 과정에도 그대로 쓰는 데 있다고 본다.


4. manifold 관점에서 보기

논문에서 말하는 manifold 는 어렵게 들리지만, 여기서는 모델이 자연스러운 이미지라고 학습한 영역 정도로 이해하면 된다. 깨끗한 이미지에는 깨끗한 이미지의 분포가 있고, 노이즈가 조금 섞인 이미지에는 그 노이즈 단계에 맞는 분포가 있다.

디퓨전 과정에서는 시간 step 마다 이미지의 상태가 다르다. 완전히 깨끗한 이미지가 있는 영역이 있고, 노이즈가 많이 섞인 이미지가 있는 영역이 있다. 각 step 마다 모델이 학습한 "그 시점에서 자연스러운 noisy image 들이 주로 놓이는 영역" 이 있다고 볼 수 있다. 논문에서 말하는 noisy data manifold 는 이 흐름과 관련된다.

자연스러운 noisy image 영역 → 다음 step 의 자연스러운 영역 → 더 깨끗한 영역

기본 디퓨전 모델은 이 영역 위에서 조금씩 이동하도록 학습된다. 현재 x_t 가 있을 때, 모델은 "이 상태에서 어떤 노이즈를 제거해야 자연스러운 이미지 쪽으로 갈 수 있는가" 를 예측한다.

그런데 CFG 는 ε_condε_uncond 의 차이를 scale 만큼 키운다. scale 이 크면 이 값은 모델이 실제 학습 중에 자주 본 범위를 넘어서는 방향이 될 수 있다. 프롬프트를 더 잘 따르게 만들지만, 동시에 sampling trajectory 가 자연스러운 noisy image 영역에서 벗어날 수 있다.

off-manifold 의 의미 — CFG++ 에서 말하는 off-manifold 는 모델이 매 step 에서 따르도록 학습한 자연스러운 noisy image 분포의 흐름에서 샘플이 벗어나는 현상을 가리킨다. CFG scale 이 커질수록 프롬프트 방향은 강해지지만, 그 방향이 항상 자연스러운 이미지 분포의 방향과 일치하는 건 아니다.

여기서 중요한 점은 CFG++ 가 manifold 를 좌표로 직접 계산하지 않는다는 거다. "지금 샘플이 manifold 안에 있는가?" 를 검사하지도 않고, 벗어났을 때 수학적으로 projection 을 수행하지도 않는다. 대신 sampler 업데이트에서 어떤 노이즈 예측값을 사용할지 바꿔, 자연스러운 분포의 흐름을 덜 벗어나게 만든다.


5. CFG++ 의 변경점

CFG++ 의 핵심은 기존 CFG 를 완전히 버리는 게 아니다. 프롬프트를 반영하기 위해 ε_cfg 를 여전히 쓴다. 다만 sampler 한 step 안에서 ε_cfg 가 맡는 역할과 ε_uncond 가 맡는 역할을 나눈다.

구분 기존 CFG CFG++
x0 추정 ε_cfg 사용. 프롬프트를 강하게 반영한 깨끗한 이미지 방향을 얻는다. ε_cfg 사용. 프롬프트 정보는 기존처럼 반영.
xt-1 생성 ε_cfg 사용. 증폭된 guidance 방향이 다음 step 이동에도 그대로 들어간다. ε_uncond 사용. 조건 없는 자연스러운 노이즈 흐름을 쓴다.

식으로 쓰면 차이가 명확해진다. 기존 CFG 는 먼저 ε_cfgx_0 를 추정한다.

x0 추정값 = (xt − σt · εcfg) / αt

그리고 다음 step 으로 이동할 때도 ε_cfg 를 넣는다.

기존 CFG:   xt-1 = αt-1 · x0 추정값 + σt-1 · εcfg

CFG++ 는 첫 번째 줄은 그대로 둔다. 프롬프트를 반영한 x_0 추정은 필요하기 때문이다. 두 번째 줄에서 ε_cfg 대신 ε_uncond 를 쓴다.

CFG++:   xt-1 = αt-1 · x0 추정값 + σt-1 · εuncond

정리하면, CFG++ 는 "프롬프트를 반영하는 부분" 과 "다음 노이즈 단계로 이동하는 부분" 을 분리한다. 프롬프트 방향을 잡을 때는 ε_cfg 를 쓰고, 다음 step 의 noisy image 를 만들 때는 ε_uncond 를 쓴다.


6. 왜 ε_uncond 가 renoising 에 적합한가

여기서 renoising 은 이미지를 다시 망가뜨린다는 뜻이 아니다. DDIM sampling 한 step 에서 x_0 를 추정한 뒤, 다음 시간 step 에 맞는 noisy state x_{t-1} 를 구성하는 과정을 말한다.

ε_cfg 는 프롬프트 방향을 강하게 만들기 위해 인위적으로 증폭된 값이다. 프롬프트를 반영한 x_0 를 얻는 데는 도움이 되지만, 다음 noisy state 를 만들 때까지 이 증폭된 값을 그대로 쓰면 trajectory 가 과하게 휘어질 수 있다.

반면 ε_uncond 는 특정 프롬프트를 밀어붙이는 값이 아니다. 같은 x_t 를 보면서, 모델이 조건 없이 학습한 이미지 분포 기준에서 예측한 노이즈다. 따라서 다음 noisy state 를 만들 때 이 값을 쓰면, 프롬프트 방향으로 과하게 증폭된 움직임을 줄이고 모델이 학습한 일반적인 noisy image 흐름을 더 따르게 된다.

성격
ε_cfg 프롬프트 방향을 강하게 반영하기 위해 외삽된 값. 텍스트 정렬에는 유리하지만 scale 이 커지면 trajectory 를 자연스러운 분포 밖으로 밀 수 있다.
ε_uncond 조건 없이 모델이 예측한 노이즈. 특정 텍스트 방향으로 과하게 밀지 않아서 noisy data manifold 의 일반적인 흐름을 더 잘 따른다.
CFG++ 방식 x0 추정에는 ε_cfg, renoising 에는 ε_uncond.

그래서 CFG++ 를 "프롬프트를 약하게 만드는 방법" 으로 이해하면 안 된다. 프롬프트 정보는 x_0 추정 단계에 남아 있다. 다만 다음 step 으로 이동하는 노이즈 성분은 조건 없는 예측을 써서 guidance 가 만든 과한 움직임을 줄이는 거다.


7. manifold 를 직접 정의하지 않는데, 어떻게?

이 부분이 가장 헷갈릴 수 있다. CFG++ 는 manifold 를 명시적인 함수나 경계로 정의하지 않는다. "이 점은 manifold 안, 저 점은 밖" 처럼 판정하는 모듈이 따로 있는 게 아니다.

대신 디퓨전 모델 자체가 이미 각 노이즈 단계의 데이터 분포를 학습하고 있다고 본다. 모델의 score 또는 noise prediction 은 현재 위치에서 더 자연스러운 데이터 분포 쪽으로 가는 방향 정보를 담는다. 이때 조건 없는 예측 ε_uncond 는 텍스트 조건을 제거한 전체 이미지 분포의 흐름을 반영한다.

CFG++ 는 이 성질을 이용한다. 직접 manifold 를 계산해서 투영하는 게 아니라, 다음 step 을 만들 때 ε_uncond 를 사용함으로써 모델이 원래 학습한 noisy data 분포의 흐름을 따르게 한다. "manifold 안으로 강제로 집어넣는다" 라기보다, "manifold 를 벗어나기 쉬운 업데이트 항을 덜 공격적인 항으로 바꾼다" 에 가깝다.

  • 직접 하는 것ε_cfg 로 프롬프트가 반영된 x_0 를 추정. 그 다음 ε_uncondx_{t-1} 를 만든다.
  • 하지 않는 것 — manifold 방정식을 구하지 않는다. 현재 점이 manifold 안인지 검사하지 않는다. 벗어난 점을 수학적으로 projection 하지 않는다.

그래서 CFG++ 의 "manifold-constrained" 라는 표현은 별도의 제약 최적화 문제를 매 step 푸는 방식으로 이해하면 안 된다. sampler 의 업데이트 구조를 바꿔서, 결과적으로 trajectory 가 noisy data manifold 에서 덜 벗어나도록 만드는 방식에 가깝다.


 

'Image Generation > Study' 카테고리의 다른 글

[Study] Scheduler  (0) 2026.04.15
[Study] Dreambooth 원리  (0) 2026.04.12
[Study] DiT: Diffusion에 Transformer를 쓰는 방식  (0) 2026.04.01
[Study] MMDiT를 이용한 SD3  (0) 2026.03.29
[Study] SDXL 살펴보기  (0) 2026.03.26