본문 바로가기
C.W.K.
Stream
Lesson 05 of 05 · published

거울은 고르는 걸 안 도와줘

~14 min · candidate-board, judgment, comparison, seed-lock, iteration, ux

Level 0툴 빌려 쓰는 사람
0 XP0/36 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"제일 새 걸 보여 주는 건 설계 결정이 아니야. 결정을 안 했을 때 나오는 모양이지. 그리고 조용히 '최신이 곧 순위' 라고 선언해 버리고."

아무도 안 고른 기본값

생성된 candidate 를 받는 작업실은 그걸 어딘가에 놓아야 해. 그리고 제일 손이 덜 가는 길은 제일 새 걸 제일 크게 보여 주거나, 최신순으로 늘어놓거나, 그냥 있던 걸 갈아치우는 거야. 중립처럼 느껴지지. 아닌데. 최신이 이기는 화면은 이미 화가 대신 순위를 정한 거야. 제일 최근 결과가 제일 흥미롭다고 정해 버린 거지. 그것도 눈에 안 보이게. 결정을 내리는 방식 중에 제일 나쁜 방식이고.

변형을 넷 뽑는 이유가 뭐야. 아직 어느 게 좋은지 모르니까 그런 거잖아. 네가 보기도 전에 '어느 거?' 에 답해 버리는 화면은 애초에 네 몫이었던 유일한 구간을 건너뛴 거야.

결과를 여럿 보여 주는 화면은 전부 순위를 담고 있어. 네 순위를 일부러 골라. 순서, 크기, 화면에 남는 것. 전부 중요도에 대한 주장이야. 네가 안 골랐으면 프레임워크 기본값이 대신 골라 준 거고, 그건 보통 최신순이야. 구현이 제일 싸니까. 이 화면이 뭐 하는 자리인지부터 정하고, 그게 배치를 정하게 해.

거울한테 없는 동사 셋

board 를 진짜로 판단하는 자리로 바꾸는 데 세 가지가 필요했어. 그리고 하나하나가 최신이 이기는 판에서는 말할 수조차 없던 동사야.

붙잡기. candidate 를 누르면 canvas 에 고정돼서 스쳐 지나가는 것이길 그만둬. 이게 없으면 새로 도착하는 것마다 네가 들여다보던 걸 밀어내. 보는 동안 계속 갈아치워지는 결과는 평가라는 걸 할 수가 없어.

견주기. canvas 를 누르고 있으면 화가 자기 Photoshop 미리보기가 나와. 손을 떼면 candidate 로 돌아오고. 슬라이더가 둘을 겹쳐서 섞어 주고. 여기서 무엇과 견주는지를 잘 봐. 다른 candidate 가 아니라 그림 자체야. 그리기 companion 이 답해야 하는 질문은 '이 넷 중에 뭐가 제일 예뻐' 가 아니거든. '이게 내가 만들던 걸 도왔어?' 지. 견주기의 반대쪽에 화가 자기 작업이 앉아 있고, 그렇게 만든 것 자체가 주장이 UI 조작으로 나타난 거야.

붙박아 두기. 카드에 잠금을 걸면 그 카드의 seed 가 이후 모든 생성의 기준 seed 가 돼. denoise 조절기 옆 칩으로 풀고.

견주기 설계가 곧 주장 설계야. 사용자가 무엇과 견주게 해 주느냐가 이 도구가 뭐 하는 물건인지에 대한 진술이야. candidate 대 candidate 로 두면 도구가 미인 대회가 돼. candidate 대 원본으로 두면 원본을 돕는 물건이 되고. 같은 이미지 넷, 같은 화면인데 완전히 다른 제품이야. 슬라이더 반대편에 뭘 앉히느냐가 전부 정해 버려.

seed 잠금이 셋 중에 제일 깊은 이유

이건 편의 기능처럼 보이는데 사실은 반복이랑 도박을 가르는 물건이야. prompt 를 살짝 건드리고 다시 생성해. 결과가 달라. 그런데 seed 도 새로 굴렀으면 이 바뀐 거고, 그 차이를 어느 쪽에도 못 돌려. 반복하는 게 아니라 분포에서 하나 뽑아 놓고 잘 나오길 비는 거지. seed 를 잠그면 실행 사이에 움직인 것 중에서 노이즈가 빠져. 남는 건 네가 실제로 바꾼 그거고. 그게 통제된 실험이고, 창작 loop 가 이미지만 쌓는 대신 앎을 쌓는 유일한 길이야.

자동 모드에서 제일 크게 갈려. 거기선 화가가 그리는 동안 companion 이 계속 다시 생성하잖아. 거기서 움직이길 바라는 변수는 canvas 야. 그림 자체, 획 하나하나. 매번 노이즈가 새로 굴러 버리면 canvas 가 기여한 몫이 매번 새 난수 벌판 밑에 파묻혀. 그러면 실시간 loop 가 응답이 아니라 슬라이드쇼를 보여 주는 거고. 자동 loop 는 언제나 candidate 를 하나만 달라고 하니까, 거기선 잠근 seed 가 잠근 그대로 쓰여.

손으로 돌리는 batch 는 일부러 만든 예외야. 버그로 접수하지 말고 왜 그런지 알아 두는 게 나아. seed 를 잠근 채로 candidate 넷을 달라고 하면, 그 batch 는 애초에 batch 인 채로 엔진에 안 가. 뇌가 그걸 엔진 job 넷으로 펼치면서 seed 를 하나씩 밀어. 그래서 job 하나하나는 여전히 고정된 seed 를 하나씩 받고. 미는 이유는 똑같은 seed 에 똑같은 prompt 로 넷을 뽑으면 같은 그림 넷이 나오기 때문이야. 그건 고를 거리가 아니잖아. 그 로직이 어디 사는지도 봐 둬. 달라고 한 작업실도 아니고 돌린 엔진도 아니고, 하나뿐인 그 문에 살아. batch 는 라우팅 결정이니까 라우팅이 있는 자리에 있는 거야. 그러니까 잠금은 기준 seed 지 얼린 seed 가 아니야. batch 가 우연이 고른 자리 말고 네가 고른 자리에서 부챗살처럼 퍼지는 거지. 변수 하나만 움직인다는 건 여전히 실행 사이 얘기고, 한 번의 실행 안에서는 퍼지는 게 목적이야.

반복 loop 는 시험 대상 하나 빼고 전부 붙박아 둬야 해. 실험의 제일 오래된 규칙인데 창작 도구에도 그대로 적용돼. 실행 사이에 임의 입력이 조용히 새로 구르는 생성 화면은 아무리 빨라도 반복을 못 받쳐 줘. 원인을 못 짚는 속도는 그냥 더 빨리 헷갈리는 방법이야.

batch, 그리고 거기 숨어 있던 버그

판단하려면 대안이 있어야 하니까 생성에 개수가 붙었어. 한 번에 하나에서 넷까지. 여기까지가 뻔한 절반이야. 안 뻔한 절반은 '끝났다' 가 무슨 뜻이냐는 거고. candidate 가 도착할 때 자기가 끝났다고 보고하는 job 은 이미지 셋이 아직 오고 있는 와중에 자동 loop 한테 빈 자리를 돌려줘. 그럼 다음 생성이 방금 띄운 batch 위에 올라타고. batch 는 약속한 candidate 가 전부 도착할 때까지 열려 있어야 해. 여기서 세는 건 장부 정리가 아니야. loop 가 자기랑 경주하지 않게 막는 물건이지.

요청 하나가 결과 N 개가 되는 순간 '끝'의 정의를 다시 뽑아. 결과 하나를 전제로 쓴 완료 로직은 거의 언제나 첫 번째에서 터져. 결과가 하나일 땐 그 둘이 같은 사건이었으니까. 요청을 batch 로 바꾸는 건 완료의 정의를 조용히 바꾸는 일이고, 거기에 걸려 있던 것들, 큐 자리든 로딩 표시든 lock 이든 전부 일찍 풀리기 시작해. 개수 파라미터를 붙이는 날 완료 신호를 전부 점검해.

잊혀야 마땅한 상태도 있어

일부러 비대칭으로 둔 게 하나 있어. 붙잡아 둔 건 저장 안 해. 보고 있는 동안만 살아 있고 candidate 를 지우거나 비우면 같이 풀려. 사용자가 건드린 건 다 저장하고 싶은 게 본능인데, 붙잡기는 약속이 아니야. '지금 이거 보고 있어' 지. 그걸 저장하면 흘긋 본 걸 화가가 한 적 없는 결정으로 바꿔 놓는 거고, 나중에 돌아왔을 때 자기가 가진 기억이 없는 선호를 주장하는 작업실을 만나게 돼. 반면에 seed 잠금이랑 batch 개수는 저장해. 그건 도구가 어떻게 굴러야 하는지에 대한 설정이니까. 같은 board, 두 종류 상태. 가르는 선은 사용자가 그게 남길 바랐느냐야.

첫 board 를 거울로 지어 놓고 얼마나 살아 있는 느낌인지 뿌듯해했어. 결과가 계속 흘러 들어오고, 항상 최신이고. 그러다 아빠가 쓰는 걸 봤는데 같은 작은 비극을 계속 반복하더라. 괜찮은 게 하나 뜨고, 아빠가 몸을 기울여 들여다보고, 결정하기 전에 다음 candidate 가 그걸 밀어내. 내 '항상 최신' 이 하필 제일 중요한 그 순간을 부수고 있었던 거야. 고칠 거리는 board 에 기능을 더 얹는 게 아니었어. 일이 고르는 건데 내가 구경하는 화면을 지어 놨다는 걸 알아채는 거였지. 살아 있음은 값어치가 있어. 근데 그게 유일한 값어치는 아니고, 화가의 주의력이랑 붙으면 언제나 져.

Code

최신순은 네가 고를 생각 없이 고른 순위야·typescript
// 최신이 이김 (얼떨결에 생긴 순위): 제일 새 candidate 가 곧 화면.
function CandidateCanvas({ candidates }) {
  const shown = candidates[candidates.length - 1];   // 최신이 곧 순위
  return <img src={shown.url} />;
}
// 화가가 하나를 들여다보려고 기울이는 순간 -> 다음 도착이 그걸 밀어냄.

// 판단하는 자리: 거울이 말 못 하던 동사 셋.
function CandidateCanvas({ candidates, pinnedId, holdingSource, blend }) {
  // 붙잡기: 붙잡아 둔 게 최신보다 위. 그래야 결과를 들여다볼 수 있음.
  const shown = pinnedId
    ? candidates.find(c => c.id === pinnedId)
    : candidates[candidates.length - 1];

  // 견주기: 슬라이더 반대편은 다른 candidate 가 아니라
  // 화가 자기 canvas. 그 선택이 곧 제품의 주장.
  return (
    <>
      <img src={sourcePreview.url} />
      <img src={shown.url} style={{ opacity: holdingSource ? 0 : blend }} />
    </>
  );
}

// 붙박아 두기: seed 잠금. 손으로 누른 것도 자동도 똑같이 적용.
const seed = lockedSeed ?? -1;   // -1 = 새로 구름. 잠그면 이게 기준 seed 가 됨.
                                 // 작업실은 이 숫자 하나만 보냄. N 개짜리
                                 // batch 는 뇌가 엔진 job N 개로 펼치면서
                                 // seed+i 로 밀어 변형이 서로 달라지게 함.
개수를 붙이면 '끝' 의 뜻이 조용히 바뀐다·typescript
// batch 완료 버그. 요청 하나에 결과 N 개 -> '끝' 을 다시 정의해야 함.

// 전 (N=1 일 땐 맞고, N 이 4 가 될 수 있는 순간 틀림):
onCandidate(c) {
  store.addCandidate(c);
  store.completeJob(c.jobId);      // 넷 중 첫 번째에서 터짐
}
// 자동 loop 는 자기 자리가 빈 걸 보고 다음 생성을 띄움.
// 이 batch 의 candidate 셋이 아직 도착하는 중인데.

// 후: job 이 자기가 약속한 개수를 알고, 다 올 때까지 안 닫음.
onJobQueued(job) {
  store.expect(job.id, job.candidateCount);   // 서버가 CLAMP 한 개수를 되돌려줌
}
onCandidate(c) {
  store.addCandidate(c);
  if (store.arrived(c.jobId) === store.expected(c.jobId)) {
    store.completeJob(c.jobId);    // 이제야 자리가 빔
  }
}
// 네가 보낸 값 말고 서버가 clamp 해서 되돌려준 값을 믿어.
// 달라고 한 개수랑 받을 개수는 다른 숫자야.

External links

Exercise

결과를 만들어 주거나 가져다주는 도구를 하나 골라서 거기 박힌 순위를 찾아봐. 뭘 제일 먼저, 제일 크게, 제일 오래 보여 줘? 그 순위가 설계된 거야, 기본값에서 물려받은 거야? 그다음엔 견주기를 찾아. 나란히 놓을 수 있는 게 뭐고, 못 놓는 게 뭐야? 견주고 싶은데 인터페이스가 불가능하게 만들어 둔 걸 하나 대 봐. 그 불가능이 누군가 자기도 모르게 내린 제품 결정이야.
Hint
물려받은 순위를 제일 빨리 찾는 법은, 네가 이전 결과를 들여다보는 동안 새 결과가 도착하면 어떻게 되는지 물어보는 거야. 새 게 자동으로 이기면 그 화면은 거울이고, 그건 아무도 안 고른 거야. 배열 순서가 고른 거지.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.