로컬 이미지 생성 모델 교체기, Animagine에서 FLUX.2 klein으로

M1 Pro 맥북의 로컬 이미지 생성 모델을 FLUX.2 klein 4B로 바꿨다. 장당 59초가 136초로 느려졌는데 뽑는 장수는 줄었다. 셋업, 문장형 프롬프트, 부분 수정, 속도 측정 기록.

aborts.403 ·
로컬 이미지 생성 모델을 Animagine XL 4.0에서 FLUX.2 klein 4B로 교체. 장당 59초에서 136초로 느려졌지만 여러 명을 한 장면에 그리고 부분 수정이 된다

로컬 이미지 생성 모델을 FLUX.2 klein 4B로 바꿨다. 장당 59초 걸리던 게 136초가 됐다. 두 배 넘게 느려졌는데, 이상하게도 한 장면을 완성하려고 뽑는 장수는 오히려 줄었다. 이틀 동안 약 120장을 뽑으며 확인한 이유를 정리한다.

구분내용
이전Animagine XL 4.0. 장당 59초, 한 장면에 1명만 안정적
문제인물을 따로 뽑아 붙이느라 생성·합성 규칙이 계속 늘어남
교체FLUX.2 klein 4B (8비트, mflux). 참고 이미지 여러 장 + 문장 프롬프트
결과장당 136초로 느려짐. 대신 여러 명을 한 번에 그리고, 부분 수정이 됨

🤔 왜 바꿨나: 붙이는 방식의 한계

지난 글에서 ChatGPT 유료 플랜 대신 맥북에서 Animagine XL을 돌린 이야기를 했다. 토큰을 아끼려고 그림은 로컬에서 뽑고, Claude Code에는 프롬프트와 배치 같은 글자만 맡기는 구조다.

Animagine은 한 장에 한 사람을 그릴 때만 안정적이었다. 두 사람이 나오는 장면은 배경 따로, 인물 따로 뽑아 누끼를 따고 코드로 붙였다. 생성 횟수는 줄었지만 붙이기 위한 규칙이 계속 늘었다. 잘린 상반신을 가구 뒤로 숨기는 다각형, 얼굴을 지키는 보호 상자, 붙인 티를 지우는 다시 그리기 단계까지.

원하는 건 빠른 모델이 아니라, 한 장면에 여러 명을 그려 주는 모델이었다.

원칙은 그대로 두었다. 생성은 로컬, Claude는 글자만.

🔍 후보 고르기: 라이선스에서 절반이 떨어졌다

블로그에 광고가 붙으니 상업 이용이 되는 모델만 쓸 수 있다. 여기서 후보가 크게 줄었다.

모델판단
FLUX.2 klein 9B❌ 비상업용 라이선스
FLUX.1 Kontext❌ 비상업용 라이선스
FLUX.2 klein 4B⭕ Apache-2.0

4B는 4단계 만에 그리는 증류 모델이다. 글로 그리기와 참고 이미지 여러 장을 보고 그리기를 한 모델이 다 한다. 예전에는 Animagine 본체에 IP-Adapter를 따로 붙여야 했던 일이다.

🖥️ 셋업: mflux로 8비트 사본을 만든다

환경은 지난번과 같은 M1 Pro 맥북 프로(내장 GPU 14코어, 메모리 32GB)다. 실행기는 애플 MLX용 FLUX 실행기인 mflux 0.24를 썼다. PyTorch가 아니라 MLX라서, 합성용 가상환경과 섞이지 않게 따로 만들었다.

uv venv --python 3.12 .venv-flux
VIRTUAL_ENV=.venv-flux uv pip install "mflux==0.24.0"

# 원본 약 16GB를 받아 8비트(약 8GB)로 저장
HF_HUB_DISABLE_XET=1 .venv-flux/bin/mflux-save \
  --model flux2-klein-4b --quantize 8 \
  --path ~/.cache/comic-studio/models/flux2-klein-4b-q8

두 가지에서 시간을 썼다.

  1. Hugging Face의 새 전송 방식(xet)이 0바이트에서 멈춘 채 움직이지 않았다. HF_HUB_DISABLE_XET=1로 끄니 받아졌다.
  2. 받는 동안 여유 공간이 약 25GB 필요하다. 8비트 사본을 만든 뒤에는 원본 캐시를 지워 8GB만 남겼다.

생성 스크립트에는 HF_HUB_OFFLINE=1을 기본으로 넣었다. 모델이 로컬에 있으니 실행할 때마다 인터넷을 확인할 이유가 없다. 모델 불러오기는 1초 안팎이었다. 한 장씩 따로 실행해도 손해가 없다.

✍️ 프롬프트가 태그에서 문장으로 바뀌었다

가장 크게 달라진 건 프롬프트 쓰는 법이다. Animagine은 1girl, solo, black hair 같은 태그를 77토큰 안에 욱여넣어야 했다. FLUX는 문장으로 장면을 설명한다.

Warm Korean slice-of-life webtoon illustration, soft cel shading,
clean line art, cozy warm lighting.
A girl's cozy bedroom at night. Front center: the girl from image 1
(9-year-old Korean girl with black bob hair ..., light blue pajamas)
stands with her arms crossed, chin up, with a confident proud smile.
On the right edge the woman from image 2 (... thin gold metal-framed
glasses, cream knit sweater) sits on the edge of the bed, nodding slowly.
Natural proportions, no text, no letters, no speech bubbles.

--ref로 참고 이미지를 넣은 순서대로 “image 1”, “image 2”라고 부르면 된다. 이게 되니까 인물을 따로 뽑아 붙일 필요가 없어졌다.

.venv-flux/bin/python gen_flux.py \
  --ref refs/girl-head.png --ref refs/woman-head.png \
  --prompt "..." --out out/c3.png --seed 31 -n 2

쓰면서 익힌 규칙이 몇 가지 있다.

  • 음수 프롬프트가 없다. 빼고 싶은 건 그릴 내용으로 바꿔 쓴다. 안경을 안 쓴 남자는 인물 설명 안에 no glasses라고 쓰면 따른다.
  • 비유를 쓰지 않는다. 모델은 말 그대로 그린다. 원하는 모습을 구체적으로 적는다.
  • 쓴 단어는 그려진다. 집 안 장면에서 “신발을 벗었다”고 쓰면 신발이 현관이 아니라 마루 한가운데 놓인다. 결국 wearing grey socks, no shoes anywhere in the picture로 바꿨다.
  • 그림 속 글자는 여전히 깨진다. 글자는 지금도 코드로 얹는다.

태그는 모델이 아는 단어를 고르는 일이었고, 문장은 장면을 설명하는 일이 됐다.

⚡ 느려졌다: 장당 136초

같은 맥북에서 조건별로 재 봤다.

FLUX.2 klein 4B 장당 생성 시간 막대 차트. 640×832 초안 57초, 896×1152 참고 이미지 없음 77초, 참고 이미지 2장 96초, 1088×1360 참고 이미지 2장 136초 기본값, 부분 수정 156초, 비교용 Animagine XL 59초

기본값은 가장 느린 축인 1088×1360으로 정했다. 인스타그램 4:5 크기에 확대 없이 그대로 들어가서 선이 또렷하다. 896×1152로 그리면 96초로 빨라지지만 1.2배 늘려야 한다.

공정하게 같은 896×1152로 비교하면 Animagine 59초, FLUX 96초다. 같은 크기에서도 1.6배 느리다. 136초는 크기를 키운 값까지 더한 숫자다.

빨리 가는 방법도 시험했는데 둘 다 버렸다.

  • 참고 이미지 빼기: 77초로 빨라지지만 인물 얼굴과 안경이 덜 닮는다.
  • 작게 초안 뽑기: 640×832는 57초다. 그런데 같은 시드라도 크기가 바뀌면 완전히 다른 그림이 나온다. 초안으로 구도를 보고 크게 다시 뽑는 방식이 안 된다.

mflux의 --small-text-encoder, --small-decoder 옵션도 봤지만 줄어드는 시간이 몇 초, 0.2초 수준이라 넣지 않았다.

✂️ 그런데 뽑는 장수는 줄었다

느려진 만큼 손해일 것 같았다. 실제로는 반대였다. 같은 장면을 두 모델로 그려 비교한 결과다.

같은 장면을 두 모델로 그린 비교. Animagine XL은 한 장면에 인물 1명만 그리고 다른 인물은 말풍선으로만 처리했고, FLUX.2 klein은 두 인물을 한 장면에 함께 그렸다

왼쪽 Animagine 버전은 두 번째 인물을 그리지 못해 화면 밖 말풍선으로 처리했다. 오른쪽 FLUX는 두 사람을 한 번에, 손까지 자연스럽게 그렸다. 팔짱 낀 손, 무릎 위에 올린 손 모두 손가락이 멀쩡했다.

한 장면에 필요한 작업이 이렇게 바뀌었다.

단계AnimagineFLUX.2 klein
배경따로 생성❌ 필요 없음
인물사람 수만큼 생성 + 누끼장면 1장에 같이
합성숨김 다각형, 위치 조정말풍선·글자만
다듬기다시 그리기 30% + 업스케일❌ 단계 삭제
후보장면마다 2~4장장면마다 2장

작업 폴더 기준으로 5장면짜리 작업 하나에 남은 생성 이미지가 Animagine 시절 평균 약 31장, FLUX로 바꾼 뒤 세 번은 19장, 19장, 24장이었다. 장당 시간은 두 배가 넘지만 다시 뽑을 일과 손볼 일이 줄었다. 붙인 티를 지우던 다시 그리기 스크립트는 통째로 지웠다.

장당 속도보다 중요한 건 한 장면이 끝날 때까지 몇 번 뽑느냐였다.

🩹 부분 수정: 표정 하나 때문에 다시 뽑지 않는다

FLUX에서 새로 생긴 기능이 부분 수정이다. 고른 그림을 참고 이미지로 넣고 바꿀 곳만 지시한다.

Keep this exact scene, characters, clothes, composition and art style
unchanged. Only change the man's face: he is completely stunned,
eyes wide open with tiny pupils, a single sweat drop on his forehead.

부분 수정 전후 비교. 왼쪽 원본은 남자가 무표정하고, 오른쪽은 표정만 놀란 얼굴과 땀방울로 바뀌었고 옷과 배경, 옆 인물은 그대로다

장당 약 156초. 처음부터 다시 뽑아 운에 맡기는 것보다 확실하다. 다만 써 보니 한계도 분명했다.

  • 픽셀 단위로 보존되지 않는다. 장면을 통째로 다시 그리는 방식이라, 수정본을 원본과 비교하면 전체의 20% 남짓이 달라졌다. 인물 위치도 조금 움직였다.
  • 고친 곳만 오려 붙이면 어긋난다. 얼굴만 잘라 원본에 붙였더니 목과 어깨가 맞지 않았다. 수정본을 통째로 쓴다.
  • 여러 명이 나오면 옆 사람도 바뀐다. 한 사람의 수염만 지우라고 했는데 옆 사람 얼굴까지 달라진 적이 있다.

🧑‍🎨 참고 이미지도 FLUX로 다시 그렸다

처음에는 Animagine으로 그린 캐릭터 그림을 그대로 참고 이미지로 넣었다. 그러자 FLUX 그림에 애니풍이 섞였다. 금테 안경이 은테처럼 나오고, 아이가 실제 나이보다 커 보이는 후보가 자주 나왔다.

그래서 참고 이미지 자체를 FLUX로 다시 그렸다. 흰 배경 전신 정면을 그린 뒤 머리 부분만 잘라 참고 이미지로 쓴다. 같은 프롬프트, 같은 시드로 비교하면 차이가 보인다.

같은 프롬프트와 시드로 그린 비교. 왼쪽은 애니풍 참고 이미지를 넣은 결과, 오른쪽은 FLUX로 다시 그린 참고 이미지를 넣은 결과로 그림체가 더 고르다

모델을 바꾸면 참고 이미지도 같은 모델로 다시 만든다. 다른 모델의 그림체가 그대로 따라 들어온다.

🧮 토큰은 어떻게 됐나

생성은 여전히 맥북에서 돌아서 이미지 생성 토큰은 0이다. Claude 쪽은 두 가지가 달라졌다.

  1. 프롬프트가 길어졌다. 태그 프롬프트는 27단어 안팎이었는데 문장 프롬프트는 장면 하나에 200단어가 넘는다. 5배 이상이다. 다만 한 장면에 수백 토큰이라, 대화 전체 사용량에서는 티가 나지 않았다.
  2. 묶어서 보는 비중이 늘었다. Claude가 결과 이미지를 열어 볼 때마다 입력 토큰이 든다. 후보 여러 장을 비교 시트 한 장으로 묶으면 한 번 값으로 끝난다. 세션 기록을 파일 이름 기준으로 세어 보니, Animagine 시절에는 이미지 열기 284회 중 묶음 시트가 약 20%였고 FLUX로 바꾼 뒤에는 100회 중 48회였다. 인물을 따로 뽑아 하나씩 확인하던 일이 사라진 영향이 크다.

생성 기록도 남기게 했다. 그림을 저장할 때마다 같은 이름의 JSON에 프롬프트, 참고 이미지, 시드, 크기, 걸린 시간을 적는다.

meta = dict(model=MODEL_PATH.name, mflux=version("mflux"), seed=seed,
            prompt=a.prompt, refs=a.ref, width=a.width, height=a.height,
            steps=a.steps, guidance=1.0, seconds=sec)
path.with_suffix(".json").write_text(json.dumps(meta, ensure_ascii=False, indent=2))

부분 수정이나 재생성을 할 때 Claude가 이 파일만 읽으면 된다. 어떤 프롬프트였는지 스크립트와 로그를 뒤질 필요가 없다.

✅ 로컬 모델을 바꾸기 전에 확인할 6가지

  1. 라이선스부터. 같은 FLUX.2 klein도 4B는 Apache-2.0, 9B는 비상업용이다.
  2. xet 전송이 멈추면 HF_HUB_DISABLE_XET=1로 받는다.
  3. 8비트로 저장. 16GB가 8GB로 줄어 32GB 맥북에서 여유가 생긴다.
  4. 크기는 최종 크기로. 크기가 바뀌면 같은 시드도 다른 그림이라 작은 초안이 소용없다.
  5. 장당 시간보다 장면당 장수. 느려도 다시 뽑을 일이 줄면 이득이다.
  6. 참고 이미지는 같은 모델로. 이전 모델 그림을 넣으면 그 그림체가 섞인다.

관련 글