CFG 스케일

CFG scale

프롬프트를 얼마나 고지식하게 따를지 정하는 숫자. 낮으면 느슨하고 자유롭게, 높으면 엄격하고 과장되게 반영합니다.

다른 이름: Classifier-free guidance scaleGuidance scale
···
html
<div class="wrap">
  <canvas id="cv"></canvas>
  <div class="panel">
    <div class="row"><span>CFG</span><b id="val">1</b></div>
    <div class="bar"><div class="fill" id="fill"></div></div>
  </div>
</div>
css
.wrap{position:relative;width:100%;height:100%}
#cv{position:absolute;inset:0;width:100%;height:100%}
.panel{position:absolute;left:10px;right:10px;bottom:10px;z-index:2;padding:8px 10px;border-radius:10px;
  background:rgba(13,13,18,0.55);backdrop-filter:blur(6px);border:1px solid rgba(255,255,255,0.15)}
.row{display:flex;align-items:center;gap:6px;font-size:11.5px;font-weight:700;color:#f1f0ec}
.bar{margin-top:5px;height:5px;border-radius:99px;background:rgba(255,255,255,0.15);overflow:hidden}
.fill{height:100%;width:5%;background:var(--accent);transition:width .08s linear}
js
const cv = document.getElementById('cv'), ctx = cv.getContext('2d');
const val = document.getElementById('val'), fill = document.getElementById('fill');
function fit() { const dpr = Math.min(devicePixelRatio || 1, 2); cv.width = innerWidth * dpr; cv.height = innerHeight * dpr; ctx.setTransform(dpr, 0, 0, dpr, 0, 0); }
addEventListener('resize', fit); fit();

function mulberry32(a) { return function () { a |= 0; a = a + 0x6D2B79F5 | 0; let t = Math.imul(a ^ a >>> 15, 1 | a); t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t; return ((t ^ t >>> 14) >>> 0) / 4294967296; }; }

function render(cfg) {
  const w = innerWidth, h = innerHeight;
  ctx.clearRect(0, 0, w, h);
  ctx.setLineDash([5, 5]); ctx.strokeStyle = 'rgba(255,255,255,0.3)'; ctx.lineWidth = 1.5;
  ctx.beginPath(); ctx.arc(w * 0.5, h * 0.55, Math.min(w, h) * 0.22, 0, Math.PI * 2); ctx.stroke();
  ctx.setLineDash([]);
  const t = cfg / 20, r = mulberry32(7)();
  const wob = 1 - t;
  const px = w * 0.5 + (r - 0.5) * w * 0.34 * wob, py = h * 0.55 + (r - 0.5) * h * 0.34 * wob;
  const rad = Math.min(w, h) * 0.22 * (0.65 + t * 0.35);
  ctx.beginPath();
  if (cfg > 16) {
    const n = 14;
    for (let i = 0; i <= n; i++) { const a = i / n * Math.PI * 2; const rr = rad * (i % 2 ? 1.3 : 1); const x = px + Math.cos(a) * rr, y = py + Math.sin(a) * rr; i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y); }
  } else {
    ctx.arc(px, py, rad, 0, Math.PI * 2);
  }
  ctx.closePath();
  ctx.fillStyle = 'hsl(265 ' + (25 + t * 60) + '% ' + (45 + t * 18) + '%)';
  ctx.fill();
}
let cfg = 1, dir = 1;
setInterval(() => {
  render(cfg); val.textContent = cfg.toFixed(0); fill.style.width = (cfg / 20 * 100) + '%';
  cfg += dir * 0.35;
  if (cfg >= 20) { cfg = 20; dir = -1; } if (cfg <= 1) { cfg = 1; dir = 1; }
}, 90);
render(1);

분류자 없는 안내(classifier-free guidance)는 매 스텝마다 노이즈 예측을 두 번 계산합니다 — 한 번은 프롬프트를 조건으로, 한 번은 조건 없이(또는 네거티브 프롬프트로). 최종 예측은 조건 없는 예측에서 시작해서 두 예측의 차이만큼 조건 쪽으로 밀어내는데, 그 "얼마나 밀어낼지"를 정하는 배율이 CFG 스케일입니다.

값이 낮으면 조건 없는 예측에 가까워서 프롬프트를 느슨하게만 반영하고, 그만큼 결과가 더 다양하고 자유롭게 나옵니다. 값이 높으면 조건 쪽으로 강하게 밀어붙여서 프롬프트에 있는 요소가 더 뚜렷하고 진하게 나타나지만, 지나치게 높이면 색이 과포화되거나 윤곽이 부자연스럽게 날카로워지는 등 과도한 안내의 부작용이 나타납니다.

적정 범위는 모델·샘플러마다 다르고 도구마다 기본값도 제각각이라 여기서 구체적인 숫자를 정답처럼 제시하지는 않습니다. 같은 프롬프트와 시드를 고정한 채 이 값만 바꿔보면서 자신의 모델에 맞는 지점을 찾는 편이 안전합니다.

아래 데모는 실제 안내 계산 대신, "목표 구도"를 점선 원으로 표시해두고 CFG 값이 오르내림에 따라 도형이 그 목표에 얼마나 가깝게·진하게 그려지는지, 너무 높을 때 윤곽이 날카롭게 깨지는지를 절차적으로 흉내 낸 시뮬레이션입니다.

언제 쓰나

프롬프트를 더 강하게 반영하고 싶을 때 올리고, 결과가 너무 과장되거나 부자연스러우면 낮춥니다. 극단으로 밀어붙이기보다 시드를 고정하고 조금씩 조정하는 편이 낫습니다.