개요

범위: ComfyUI 워크플로가 한 그래프에 다 들어가지 않게 된 시점부터의 설계 원칙. JSON 구조를 이해하는 법, 그룹 단위로 쪼개는 법, 파이프라인을 여러 JSON 으로 나누는 경계, 파일명 규약(CID) 까지.

대상: 한두 번 돌려보고 나서 “이걸 어떻게 관리하지” 라는 질문에 도달한 사람. 여러 스타일·해상도의 자산을 같은 캐릭터 기준으로 묶어 뽑는 상황이라면 더 직접적인 도움.

전제: 환경 셋업 완료, 기본 노드(KSampler · VAE · CLIPTextEncode) 사용법 숙지. JSON 포맷은 대충 본 적 있음.

배경

ComfyUI 의 워크플로는 화면에 보이는 그래프 전체가 곧 JSON 데이터다. UI 가 내보내는 파일과 /prompt API 가 받는 페이로드가 근본적으로 같은 구조라서, 저장 · 공유 · 버전 관리 · 자동화를 전부 일반 파일처럼 다룰 수 있다. Blender 의 셰이더나 Unreal 블루프린트도 비슷한 직렬화 전통을 따르는데, ComfyUI 쪽은 포맷이 단순한 딕셔너리라 JQ 로 바로 쿼리할 수 있다.

이 자유도는 규모가 커지는 순간 반대 방향으로도 작용한다. 노드 50 개 넘는 그래프에서 파라미터 하나 건드릴 때마다 전체가 재실행되고, 두 사람이 같은 JSON 을 고치면 diff 충돌, 어디가 범인인지 좁히기만 반나절. 설계 원칙이 없으면 그래프는 결국 일회용 스케치가 된다. 아래 규약들은 그 지점까지 가지 않게 막는 최소 장치.

설계 규약

여섯 가지. 12 는 JSON 포맷 이해, 34 는 구조 쪼개기, 5 는 프롬프트 축, 6 은 repo 관리.

1. API Format 과 Editor(Save) Format 을 구분한다

ComfyUI UI 의 저장 버튼 두 개는 다른 포맷을 뱉는다.

버튼포맷내용용도
Save (API Format)실행 포맷노드 그래프 DAG 만 (class_type, inputs)/prompt API 호출, 자동화, 스크립트
Save편집 포맷위 + 시각 배치(pos, size), 위젯 값, 그룹 박스Editor 편집 재개, 사람이 열어볼 때

API Format 의 최소 예:

{
  "3": {
    "inputs": {
      "seed": 42,
      "steps": 20,
      "cfg": 7.0,
      "sampler_name": "euler",
      "scheduler": "normal",
      "denoise": 1.0,
      "model": ["4", 0],
      "positive": ["6", 0],
      "negative": ["7", 0],
      "latent_image": ["5", 0]
    },
    "class_type": "KSampler"
  },
  "4": {
    "inputs": { "ckpt_name": "sd_xl_base_1.0.safetensors" },
    "class_type": "CheckpointLoaderSimple"
  }
}

"3", "4" 는 노드 ID. inputs 의 각 값은 리터럴 또는 [다른노드ID, 출력인덱스] 꼴의 링크. DAG 가 유효해야 실행된다.

왜 구분: API Format 은 스크립트 · CI 에 넣기 쉽고 diff 도 작다. Editor Format 은 사람이 여는 용이라 커밋 diff 가 크고 위젯 위치 바뀔 때마다 변경이 생긴다. repo 에 올릴 때는 API Format, 편집 중 중간 상태는 Editor Format 로 로컬에.

2. 그룹 박스로 실행 순서를 선형화한다

ComfyUI 의 그룹 박스는 단순 시각 그룹이 아니라 실행 단위·모델 관리 힌트에 가깝다. G0 → G1 → G2 식으로 이름을 매기고 입력·출력이 한 방향으로 흐르게 짜면, 그래프를 열었을 때 머릿속에서 선형으로 따라간다.

실제 파이프라인의 예 — WF1 portrait-npc.json:

G0    로더 (체크포인트 3개, LoRA, ControlNet, IPAdapter, VAE)
G0.5  얼굴 YOLO → 얼굴 crop → IPAdapter 얼굴 임베딩
G1    실사 반신상
G2    G1 결과를 img2img (denoise 0.55, 스타일만 변환)
G3    G2 결과 배경 제거
G3-2  흉상 크롭
G4    차렷자세 전신 + 배경 제거

G1 → G0.5 (얼굴 임베딩) → G2 / G4 에 재사용되는 구조. 선형 아닌 노드는 “재사용” 으로 표시되는 얼굴 임베딩 하나뿐이다.

왜 선형화: ComfyUI 는 그룹 단위로 필요한 모델만 VRAM 에 올리고 내린다. 그룹이 뒤섞여 있으면 같은 모델을 여러 번 로드·언로드하게 되고, 타이밍을 예측하기 힘들어 디버그가 어려워진다. 선형이면 “여기까지 돌고 나서 어떤 모델이 올라가 있는지” 가 명확.

경계 예외: 얼굴 임베딩처럼 “재사용” 되는 데이터는 한 곳에서 만들고 여러 그룹이 읽게 둔다. 만드는 지점을 그룹 하나로 격리만 하면 선형성을 크게 해치지 않는다.

3. 파이프라인을 단계별 JSON 으로 쪼갠다

한 그래프에 모든 단계를 몰아넣으면 규모가 커질수록 다음이 쌓인다.

  • 파라미터 하나 튜닝이 전체 재실행을 요구
  • 외부 API 호출·고비용 연산이 뒷쪽에 있으면 앞쪽 실패 시 크레딧·시간 낭비
  • “기반 이미지 생성” 같은 공통 단계를 다른 용도로 재사용하려 복붙이 시작
  • 한 JSON 을 둘이 편집하면 diff 충돌
  • 문제 발생 시 어느 노드가 범인인지 좁히는 데만 반나절

해결은 파이프라인을 의미 단위로 쪼개고 중간 산출물을 파일시스템으로 넘기는 것.

WF1  베이스 이미지 생성     input: 프롬프트          output: base.png
WF2  픽셀아트 변환          input: base.png          output: pixel_64.png
WF3  8방향 로테이션         input: pixel_64.png      output: rot_*.png × 8
WF4  애니메이션 GIF 합성    input: rot_*.png × 8     output: anim.gif

각 단계가 독립 JSON 이라 실행·평가·재실행이 독립. WF2 결과가 마음에 안 들면 WF1 을 다시 돌릴 필요 없이 WF2 파라미터만 바꿔 재시도.

왜 파일시스템: Latent 는 프로세스 로컬이라 WF1 끝나고 WF2 가 이어받기 어렵다. PNG 로 떨어뜨리면 언제든 다시 읽을 수 있고, 중간 결과를 디스크에서 눈으로 확인 가능.

쪼갤 때의 경계 기준:

  • 자르기 좋은 지점 — 파일로 넘길 수 있는 타입 경계(이미지·텍스트), 외부 API 호출 전후(재시도·복구를 쉽게), 자주 튜닝하는 파라미터 몰리는 구간 앞뒤
  • 자르면 안 되는 지점 — Latent 상태에서만 유효한 중간 값(파일로 넘기려면 VAE 디코드 비용), 랜덤 시드에 민감한 연속 노드(분리하면 재현성 훼손)

4. CID 파일명 규약 — 여러 실행을 섞이지 않게

파이프라인이 단계별로 쪼개졌으니 중간 산출물이 디스크에 쌓인다. 여러 캐릭터·여러 실험을 돌리면 output/ 이 금방 난장판이 된다. 해결은 한 실행 시퀀스를 묶는 공통 ID.

포맷: <CID>_<단계>_<세부>_<seq>.png. CID 는 slug_YYYYMMDD_rand6 같이 slug + 날짜 + 랜덤 꼬리표.

output/
├── aria-king_20260422_a3f9k2_realistic-halfbody_00001.png      # WF1 G1
├── aria-king_20260422_a3f9k2_illustration-halfbody_00001.png   # WF1 G2
├── aria-king_20260422_a3f9k2_illustration-halfbody-nobg_00001.png  # WF1 G3
├── aria-king_20260422_a3f9k2_bust_00001.png                    # WF1 G3-2
├── aria-king_20260422_a3f9k2_illustration-attention-nobg_00001.png # WF1 G4
├── aria-king_20260422_a3f9k2_pixel_64_00001.png                # WF2
└── aria-king_20260422_a3f9k2_rot_east_00001.png                # WF3

왜 이 포맷: slug(aria-king)로 사람이 어느 NPC 인지 바로 알아보고, 날짜로 세대(한 NPC 를 여러 번 튜닝하면 세대가 다름)를 구분, 랜덤 꼬리표로 같은 날 같은 slug 여러 번 돌려도 충돌 없음. <단계>_<세부> 는 파이프라인 안에서의 위치.

CID 를 파이프라인 전체에서 딱 한 번 생성하고, 각 워크플로의 입력 노드(PrimitiveNode 또는 String Literal)에 넣어 SaveImagefilename_prefix 로 흘려보낸다. 이렇게 해야 WF1 ~ WF4 산출물이 같은 접두어로 묶인다.

5. 프롬프트를 고정축·가변축으로 분리

CLIPTextEncode 에 프롬프트를 한 줄로 넣으면 새 NPC 만들 때마다 캐릭터 특성 + 구도 + 스타일 + 품질 태그를 전부 같이 고치게 된다. 스타일만 바꾸려 했는데 구도까지 뒤틀림.

분리 규약:

char_positive  = "1girl, silver hair, stern expression, blue eyes"
char_negative  = "smile, male"
comp_positive  = "masterpiece, best quality, solo, upper body, centered, looking at viewer"
comp_negative  = "lowres, blurry, bad anatomy"
  • char_* — 캐릭터 고유. NPC 별로 건드림
  • comp_* — 구도 · 품질 · 스타일. 프로젝트 전체 공통, 거의 안 건드림

두 조각을 ConditioningConcat 으로 합쳐 KSampler.positive/negative 로 보낸다.

왜 이 분리: NPC 별로 char_* 두 자리만 수정하면 모든 산출물의 톤이 저절로 일관. 프로젝트 스타일 튜닝은 comp_* 한 번 확정해두고 잊어버리기. 한 워크플로에 CLIPTextEncode 4 개 · ConditioningConcat 2 개가 박히지만, 그 대가로 “한 자리만 건드리면 된다” 는 생산성이 생긴다.

6. Git 으로 관리할 때의 하이진

워크플로 JSON 을 repo 에 올리는 게 전제라면 몇 가지 손질이 필요하다.

  • 노드 ID 정규화: UI 저장 시 ID 가 재배치되면 diff 가 전부 거짓 변경이 된다. 수동으로 ID 를 박아 두거나, 저장 후 스크립트로 오름차순 정렬·리매핑.
  • 체크포인트·모델 이름은 상대 이식 가능한 값으로: 로컬 머신 절대경로(D:\AI\models\...)가 박히면 다른 머신 로드 실패. CheckpointLoaderSimple 처럼 파일명만 받는 노드를 쓰고, 실제 경로 해석은 extra_model_paths.yaml 에 맡긴다.
  • 토큰·키 분리: 커스텀 노드 위젯에 API 키를 직접 박지 않는다. 환경변수 또는 .env 파일로 뽑고, 위젯 값은 ${env:API_KEY} 같은 간접 참조가 가능한 노드를 쓰거나, 커밋 전 해당 필드를 빈 문자열로 치환하는 전처리를 건다.
  • JSON 정렬·들여쓰기 고정: jq --sort-keys . workflow.json > fmt.json && mv fmt.json workflow.json 을 pre-commit 훅으로. diff 가 의미를 되찾는다.
  • 실험 중 워크플로는 _drafts/, 확정된 것만 루트: ComfyUI 의 user/default/workflows/_drafts/ 규약 또는 별도 폴더. UI 의 “My Workflows” 목록이 깔끔해진다.
  • PNG 메타데이터 정리: ComfyUI PNG 출력에는 워크플로 JSON 이 임베드되고 거기에 개인 모델 경로·실험 중 프롬프트가 들어간다. 공개 배포 전에 exiftool -all= -overwrite_original sample.png 로 정리.

자주 틀리는 설계 지점

증상 — 파라미터 하나 바꾸려고 전체 워크플로가 10 분간 재실행

원인: 모든 단계를 한 그래프에 몰아넣음. 샘플러 cfg 만 바꿔도 앞 단계 로더 · 인코더가 전부 재평가.

해결: 파이프라인을 의미 단위로 쪼개 파일시스템 경계에 맞춰 JSON 을 분리 (§3). 튜닝하려는 단계만 독립 실행되게.

재발 방지 · 확인 방법: 새 워크플로 설계 시 “이 단계의 출력이 파일로 떨어뜨릴 가치가 있는가” 를 먼저 자문. 예이면 경계 후보.

증상 — output/ 에 쌓인 파일명이 ComfyUI_00001_.png, ComfyUI_00002_.png

원인: SaveImagefilename_prefix 가 기본값. 어느 NPC · 어느 실행 · 어느 단계 산출물인지 파일명만 보고 알 수 없음.

해결: CID 규약 도입 (§4). <CID>_<단계>_<세부> 접두어로 통일.

재발 방지 · 확인 방법: 새 워크플로의 모든 SaveImage 노드는 filename_prefix 가 CID 를 받는 입력에 연결되도록 패턴화. 연결 안 된 SaveImage 가 있으면 빨간색으로 표시되게 템플릿 색상 규약을 정해 둠.

증상 — 새 NPC 만들 때마다 프롬프트를 처음부터 다시 짬

원인: 프롬프트가 캐릭터 + 구도 + 스타일 + 품질 태그 덩어리로 한 줄 CLIPTextEncode 에 들어있음. NPC 바꾸려면 전체를 다시 검토.

해결: char_*comp_* 로 분리해 ConditioningConcat 로 합치기 (§5). NPC 별 변경 자리를 두 곳으로 한정.

재발 방지 · 확인 방법: 새 워크플로 템플릿에 char_positive / char_negative / comp_positive / comp_negative 네 개 CLIPTextEncode 를 기본으로 두고 시작.

증상 — 친구 머신에서 JSON 로드했더니 Failed to load checkpoint: D:\AI\models\checkpoints\realvisxl_v4.safetensors

원인: 노드 위젯에 로컬 절대경로가 박혀 있음. 또는 extra_model_paths.yaml 설정이 친구 머신에 없음.

해결:

  1. 체크포인트 이름만 저장되는 노드(CheckpointLoaderSimple) 사용 — 실제 경로 해석은 각 머신의 extra_model_paths.yaml 이 담당
  2. 공유 전 JSON 에 절대경로가 박혀있지 않은지 grep -E '[A-Z]:\\\\|/Users/|/home/' workflow.json 으로 점검

재발 방지 · 확인 방법: pre-commit 훅 또는 CI 에서 JSON 내 절대경로 패턴을 감지해 push 차단.

증상 — git diff workflow.json 이 매번 100 줄씩 변경으로 나옴

원인: UI 저장 시 노드 ID 재배치 + JSON 키 순서 비결정성. 실제 로직은 안 변했는데 diff 는 전체가 바뀐 것처럼 보임.

해결: jq --sort-keys . 로 키 정렬, 저장 후 스크립트로 노드 ID 리넘버링. pre-commit 훅으로 자동화.

재발 방지 · 확인 방법: .git/hooks/pre-commitjq 호출을 박거나 pre-commit 훅으로 관리. 첫 commit 때 정렬된 상태로 커밋하고 이후는 저절로.

증상 — 공개 저장소에 올린 PNG 에 내 로컬 모델 경로가 포함돼 있음

원인: ComfyUI 가 출력 PNG 메타데이터에 워크플로 JSON 전체를 embedding — 개인 파일 경로·실험 프롬프트 노출.

해결: 공개 전 exiftool -all= -overwrite_original image.png 로 메타데이터 전체 제거. 또는 exiftool -Parameters= -Workflow= -Prompt= image.png 로 ComfyUI 관련만 선별 제거.

재발 방지 · 확인 방법: 공개용 이미지를 별도 폴더(public_samples/)에 복사한 뒤 일괄 exiftool 처리. 이 폴더만 외부로 공유.

관련

  • 환경 셋업 편 — 이 원칙들이 돌아가는 전제 환경
  • 커스텀 노드 작성 편 — JSON 구조를 이해한 뒤 노드를 확장하는 쪽
  • ComfyUI 공식 워크플로 문서 / https://docs.comfy.org/essentials/workflow — 워크플로 포맷 · API 레퍼런스
  • exiftool / https://exiftool.org/ — PNG 메타데이터 조회·편집
  • jq / https://jqlang.org/ — JSON 정렬·쿼리·변환