로라

LoRA

큰 모델 전체를 다시 학습하지 않고, 작은 추가 가중치만 얹어서 특정 스타일·캐릭터를 재현하게 만드는 경량 파인튜닝 방식.

다른 이름: Low-Rank AdaptationStyle adapter
···
html
<div class="wrap"><canvas id="cv"></canvas><span class="tag" id="tag">LoRA: 파스텔</span></div>
css
.wrap{position:relative;width:100%;height:100%}
#cv{position:absolute;inset:0;width:100%;height:100%}
.tag{position:absolute;top:8px;left:8px;z-index:2;font-size:10.5px;font-weight:700;color:#f1f0ec;
  padding:5px 9px;border-radius:8px;background:rgba(13,13,18,0.55);border:1px solid rgba(255,255,255,0.15)}
js
const cv = document.getElementById('cv'), ctx = cv.getContext('2d');
const tag = document.getElementById('tag');
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; }; }

let layers = [], sun = null;
function computeLayout(w, h) {
  const r = mulberry32(21);
  layers = [];
  for (let l = 0; l < 3; l++) {
    const pts = [];
    for (let i = 0; i <= 7; i++) { const x = w * i / 7; const y = h * (0.5 + l * 0.14) - r() * h * 0.12; pts.push([x, y]); }
    layers.push(pts);
  }
  sun = [w * (0.3 + r() * 0.4), h * (0.2 + r() * 0.1)];
}

function drawPath(pts, h) { ctx.beginPath(); ctx.moveTo(0, h); pts.forEach(([x, y]) => ctx.lineTo(x, y)); ctx.lineTo(pts[pts.length - 1][0], h); ctx.closePath(); }

function stylePastel(w, h) {
  ctx.fillStyle = 'hsl(210 40% 88%)'; ctx.fillRect(0, 0, w, h);
  ctx.beginPath(); ctx.arc(sun[0], sun[1], Math.min(w, h) * 0.08, 0, Math.PI * 2); ctx.fillStyle = 'hsl(45 70% 80%)'; ctx.fill();
  layers.forEach((pts, l) => { drawPath(pts, h); ctx.fillStyle = 'hsl(' + (200 - l * 30) + ' 35% ' + (75 - l * 15) + '%)'; ctx.fill(); });
}
function styleInk(w, h) {
  ctx.fillStyle = '#111116'; ctx.fillRect(0, 0, w, h);
  ctx.strokeStyle = '#f1f0ec'; ctx.lineWidth = 1.4;
  ctx.beginPath(); ctx.arc(sun[0], sun[1], Math.min(w, h) * 0.08, 0, Math.PI * 2); ctx.stroke();
  layers.forEach((pts, l) => {
    ctx.beginPath(); ctx.moveTo(pts[0][0], pts[0][1]); pts.forEach(([x, y]) => ctx.lineTo(x, y)); ctx.stroke();
    ctx.strokeStyle = 'rgba(241,240,236,' + (0.35 - l * 0.08) + ')'; ctx.lineWidth = 0.8;
    for (let x = 0; x < w; x += 10) { ctx.beginPath(); ctx.moveTo(x, h * (0.62 + l * 0.14)); ctx.lineTo(x - 10, h); ctx.stroke(); }
    ctx.strokeStyle = '#f1f0ec'; ctx.lineWidth = 1.4;
  });
}
function styleNeon(w, h) {
  ctx.fillStyle = '#08060f'; ctx.fillRect(0, 0, w, h);
  ctx.shadowColor = 'hsl(300 90% 60%)'; ctx.shadowBlur = 14;
  ctx.strokeStyle = 'hsl(300 90% 65%)'; ctx.lineWidth = 2;
  ctx.beginPath(); ctx.arc(sun[0], sun[1], Math.min(w, h) * 0.08, 0, Math.PI * 2); ctx.stroke();
  layers.forEach((pts, l) => { ctx.shadowColor = 'hsl(' + (180 + l * 40) + ' 90% 60%)'; ctx.strokeStyle = 'hsl(' + (180 + l * 40) + ' 90% 65%)'; ctx.beginPath(); ctx.moveTo(pts[0][0], pts[0][1]); pts.forEach(([x, y]) => ctx.lineTo(x, y)); ctx.stroke(); });
  ctx.shadowBlur = 0;
}
const styles = [{ fn: stylePastel, name: '파스텔' }, { fn: styleInk, name: '잉크 라인' }, { fn: styleNeon, name: '네온 글로우' }];
let si = 0;
function render() {
  const w = innerWidth, h = innerHeight;
  computeLayout(w, h);
  styles[si].fn(w, h);
  tag.textContent = 'LoRA: ' + styles[si].name;
}
render();
setInterval(() => { si = (si + 1) % styles.length; render(); }, 2600);

LoRA(Low-Rank Adaptation)는 기반 모델의 가중치는 그대로 얼려두고, 그 층마다 작은 저랭크 행렬 한 쌍을 덧붙여 그 적은 파라미터만 학습합니다. 기반 모델 전체를 다시 학습하는 것보다 필요한 데이터·연산이 훨씬 적어서, 특정 화풍이나 캐릭터를 적은 이미지 묶음만으로도 흉내 낼 수 있습니다.

생성할 때는 기반 모델 위에 이 LoRA를 얹었다 뗐다 할 수 있고, 보통 "얼마나 강하게 반영할지"를 정하는 가중치 다이얼도 함께 딸려 있습니다. 여러 LoRA를 동시에 얹어서 스타일과 캐릭터를 동시에 재현하는 식으로 조합할 수도 있습니다.

기반 모델 위에 얹는 덧셈에 가까운 구조라서, 기반 모델의 전반적인 구도·해부학적 성향은 그대로 남고 "어떻게 그려지는지"만 LoRA 쪽으로 기울어집니다. 여러 LoRA를 겹치거나 가중치를 지나치게 높이면 서로 충돌하거나 프롬프트를 압도해버릴 수 있습니다.

아래 데모는 실제 가중치 주입 대신, 완전히 같은 좌표로 계산한 고정된 구도 위에 렌더링 방식(단색 채우기·선화·네온 발광)만 바꿔 가며 그려서 "구조는 그대로, 스타일만 바뀐다"를 흉내 낸 시뮬레이션입니다.

언제 쓰나

특정 화풍·캐릭터를 반복해서 재현하고 싶은데 그때마다 프롬프트로 길게 설명하기 번거로울 때 씁니다. 기반 모델을 바꾸면 같은 LoRA라도 결과가 달라질 수 있다는 점은 감안합니다.