
M1 Mac에서 ComfyUI의 실사 이미지가 흐릿하게 나오는 이유: 원인은 단일 단계 KSampler
요약
M1 Mac 환경의 ComfyUI에서 실사 이미지 생성 시 발생하는 디테일 뭉개짐 문제를 해결하기 위한 2단계 고해상도 수정(2-pass Hires fix) 워크플로우를 소개합니다. 단일 단계 KSampler의 한계를 극복하기 위해 FreeU_V2와 4x-UltraSharp를 결합한 최적의 설정값을 공유합니다.
핵심 포인트
- 단일 단계 KSampler는 실사 이미지의 질감과 디테일을 뭉개뜨리는 원인이 됨
- 2단계 Hires fix를 통해 작은 베이스 생성 후 단계적 업스케일링 권장
- 두 번째 패스에서 디노이즈(denoise) 값을 0.42로 조절하여 선명도 확보
- M1 Mac의 메모리 효율과 이미지 품질을 동시에 잡는 워크플로우 구성
원래 Zenn (일본어)에 게시되었습니다. 이곳에 교차 게시되었습니다.
ComfyUI에서 실사 이미지를 생성하려고 하면 "디테일이 뭉개지고(soft detail)", "얼굴이 깨지며", "해상도를 높이면 모든 것이 무너지는" 현상을 겪게 됩니다. 대부분의 경우 그 원인은 **단일 단계 KSampler (single-shot KSampler)**입니다. 이 글에서 저는 이를 2단계 고해상도 수정 (2-pass Hires fix) + FreeU_V2 + 4x-UltraSharp 구조로 재구축하였고, 실제로 M1 Mac (ComfyUI :8000)에서 실행했습니다. 또한 2688×3456 출력까지 재현할 수 있도록 **모든 노드와 모든 파라미터 (시드 포함)**를 공개합니다.
단 하나만 기억해야 한다면 이것입니다: **"작은 베이스를 생성하고, 단계적으로 업스케일링하며, 두 번째 패스에서 디노이즈(denoise) 값을 1.0에서 0.42로 조절하여 선명도를 높이는 2단계 접근 방식"**은 로컬 환경의 실사 생성 시 발생하는 붕괴 현상을 제거하기 위한 최소한의 재현 가능한 설정입니다. 이 글은 2단계 Hires까지의 "기초"만을 다룹니다. 페이스 락킹 (Face locking, IPAdapter / PuLID) — 모델은 이미 준비되어 있지만 이 글에서는 사용되지 않습니다 = 다음 단계 — 이며, 저는 글의 후반부에서 이 경계를 솔직하게 긋습니다.
미리 알려드립니다: 이 글은 설정값과 시각적 비교를 기록한 실습 기록입니다. 생성 시간, VRAM / 통합 메모리(unified memory) 소비량, it/s, 그리고 단일 단계 버전 대비 정량적 점수는 모두 측정하지 않았으므로 수치를 인용하지 않겠습니다. 제가 인용하는 유일한 숫자는 워크플로우(WF) JSON의 실제 값과 출력된 PNG의 측정된 픽셀 값뿐입니다.
문제점: 단일 단계 KSampler가 실사 이미지를 흐릿하고 깨지게 만듭니다
SDXL 실사 모델을 로드하고 가장 짧은 경로로 생성합니다: Checkpoint → KSampler → VAEDecode → Save. 많은 튜토리얼이 이 형태를 따릅니다. 하지만 결과물은 "기본 해상도에 갇혀 있고, 피부 표현이 평면적이며, 확대했을 때 깨지는" 이미지가 됩니다. 제가 단일 단계(single-shot) 버전에서 확인한 구체적인 붕괴 현상은 두 가지였습니다: 피부가 평면적이고 플라스틱처럼 변하며 모공과 질감을 잃는 것, 그리고 1:1 비율 또는 그 이상에서 머리카락 가닥과 윤곽선 가장자리가 흐릿해지고 뭉개지는 것입니다. 이 상태에서 업스케일(upscale)을 적용하는 것은 흐릿한 정보를 늘릴 뿐이며, 디테일은 결코 되살아나지 않습니다.
이러한 구성은 저희 내부 디자인 부서의 원칙에서도 명시적으로 금지되어 있습니다. 실사 및 다이어그램 작업에 대한 워크플로우 정책을 정의하는 공식 문서에는 다음과 같이 명시되어 있습니다:
금지 사항: 교과서적인 단일 단계 KSampler (전문적인 다단계 방식 = Hires fix 2 passes + IPAdapter / PuLID face locking + 4x-UltraSharp 사용 필수).
— 내부 디자인 부서의 공식 실사 워크플로우 중 ("단일 단계 KSampler는 금지됨")
왜 단일 단계 방식이 나쁠까요? SDXL은 1024px급 해상도를 중심으로 학습되었기 때문에, 최종 해상도를 곧바로 목표로 하면 구도와 디테일 모두 안정되지 않습니다. 반면, 고해상도의 EmptyLatent를 한 번에 몰아붙이면 이미지가 깨지거나(예: 동일한 얼굴이 두 번 나타남), M1의 메모리를 한꺼번에 점유해 버립니다. 이 두 가지 문제를 동시에 피할 수 있는 방법이 바로 2단계(2-pass) Hires fix입니다. 아래의 비교 그림과 크롭(crop) 이미지에서 그 차이를 확인해 보십시오.
전체적인 해결책: 2-pass Hires fix + FreeU_V2 + 4x-UltraSharp
단일 단계 방식을 버리고, 세 가지 무기를 추가했습니다.
- 2단계(Two passes): 베이스를 약간 작게(896×1152) 생성한 뒤, Latent를 1.5배 업스케일하고, 두 번째 단계에서 디테일을 추가합니다.
- FreeU_V2: UNet의 스킵 연결(skip connections)을 조정하여 디테일을 높이는 은밀한 기술입니다.
- 4x-UltraSharp: 픽셀 공간에서 4배로 높인 후, 다시 0.5배로 낮추어 디테일을 응축시킵니다.
모델은 용도에 따라 교체됩니다. 실사(Photorealistic) = RealVisXL_V5.0_fp16; 다이어그램 및 이미지 내 텍스트(text-in-image) = flux1-dev. SDXL 제품군은 다이어그램의 가는 선과 문자를 표현하는 데 취약하므로, 실사 작업과 다이어그램 작업을 동일한 모델로 강제하지 않는 것이 기준입니다 (이 글은 실사 = RealVisXL 라인을 다룹니다).

그림 ① (검증됨 — 이 글의 시각적 검증의 핵심): 단일 단계 (single-shot) KSampler (왼쪽 / 나쁜 버전)와 실제 2단계 (2-pass) Hires 설정 (오른쪽 / 좋은 버전)의 노드 흐름 비교. 노드 이름과 파라미터는 PRO_RealVisXL_2pass.json의 실제 값입니다.
전체 노드 구성 및 실제 파라미터 (그대로 재현 가능)
제가 실제로 실행한 PRO_RealVisXL_2pass.json의 노드 순서를 위에서 아래로 실제 값과 함께 나열합니다. 동일한 값으로 빌드하면 동일한 체인을 얻을 수 있습니다 (:8000에 입력되는 API 형식 JSON과 Python 스크립트 pro_gen_realvisxl_2pass.py는 동일한 파라미터와 일치합니다).
| # | class_type | 주요 입력값 (key inputs, 실제 값) |
|---|---|---|
| 1 | CheckpointLoaderSimple | RealVisXL_V5.0_fp16.safetensors |
| ... |
두 KSampler 사이의 차이점만 추출하면 다음과 같습니다. 이것이 2단계 (2-pass) 접근 방식의 핵심입니다.
# 1단계 (Pass 1): 처음부터 구도를 생성 (denoise = 1.0)
KSampler(seed=770077, steps=32, cfg=5.5,
sampler_name="dpmpp_2m_sde", scheduler="karras", denoise=1.0)
...
프롬프트의 핵심은 "피부 질감을 명시적으로 요구하고, 부정 프롬프트 (negative prompt)를 통해 플라스틱 같은 피부와 3D 렌더링 느낌을 제거하는 것"입니다.
positive: … detailed realistic skin texture with pores, film grain, 85mm lens …
negative: worst quality, blurry, deformed hands, plastic skin, waxy, cartoon, 3d render …
해상도 체인: 실제 값으로부터 계산됨 (측정된 출력값과 일치)
각 노드의 스케일 (scale) 값을 곱하면 최종 해상도가 나옵니다. 이는 출력된 PNG의 측정된 픽셀 수와 정확히 일치합니다.

그림 ② (검증됨): 해상도 체인. 896×1152 →(latent×1.5)→ 1344×1728 →(4x-UltraSharp)→ 5376×6912 →(area×0.5)→ 2688×3456. 이 계산된 값은 sips로 측정한 출력 PNG의 픽셀과 일치합니다.
왜 이 특정 단계들을 사용하는가 (실제 값에서 읽을 수 있는 설계 의도)
저는 파라미터의 "이유"를 추측이 아닌, 실제 값으로부터 명시할 수 있는 범위 내에서만 기술합니다.
denoise 1.0 → 0.42의 의미
1단계(Pass 1)는 denoise=1.0으로, 노이즈로부터 구성을 완전히 구축합니다. 2단계(Pass 2)는 denoise=0.42로, 디테일을 추가하기 위해 입력값(업스케일된 latent)의 42%만을 파괴합니다. 이 값을 너무 높게 설정하면 1단계의 구성과 얼굴이 변하고, 너무 낮게 설정하면 단순히 흐릿한 업스케일링이 됩니다. 0.42는 "구성을 유지하면서 디테일을 레이어링하는" 실질적인 작동 값입니다.
왜 4배(4×)로 높인 후 0.5배(0.5×)로 조이는가
4x-UltraSharp를 사용하여 한 번 5376×6912로 높인 다음, area 방식을 사용하여 0.5배로 다시 되돌립니다. 처음부터 유효한 2배(2×)를 목표로 하는 대신, 의도적으로 과도한 해상도를 생성한 다음 이를 축소하는 방식은 면적 평균화(area averaging)를 통해 디테일을 응축하며, 업스케일러 특유의 가장자리 계단 현상(jaggies)과 노이즈를 매끄럽게 다듬어 줍니다. 유효 배율은 2배이지만, 경로에 따라 선명해지는 방식이 달라집니다.
베이스를 896×1152로 유지하고 latent×1.5로 단계를 높이는 2단계 접근 방식
처음부터 커다란 EmptyLatent (빈 잠재 공간)를 밀어붙이는 대신, 896×1152 → latent×1.5 단계를 거칩니다. 이는 M1의 통합 메모리 (Unified Memory)를 한꺼번에 점유하는 것을 피하기 위한 설계로 읽힙니다. 다만, OOM (Out of Memory)을 방지하기 위한 인과 관계는 설계 수준의 추론이며, VRAM / 메모리 소비량은 측정되지 않았습니다. 여기서는 사실(
제가 실제로 실행한 항목들은 다음과 같습니다. 출력된 2개의 PNG 파일의 측정된 픽셀은 워크플로우 (WF)의 계산된 체인과 일치합니다 (그림 ②). 이것이 "재현된다"는 증거입니다.
| 실제 항목 / 의존성 | 측정값 |
|---|---|
| PRO_RealVisXL_2pass_00001_.png (7월 13일) | sips 측정 결과 2688×3456 · 약 11.8MB |
| ... | |
![]() | |
| _단일 단계 (single-shot) KSampler 출력물 (왼쪽 · denoise 1.0 · Hires 미사용 · 896×1152를 단순히 확대함)과 2단계 (2-pass) Hires + 4x-UltraSharp 출력물 (오른쪽)의 1:1 크롭 비교 (동일 시드(seed)=770077, 동일 프롬프트, 동일 크롭 위치). 왼쪽은 윤곽선, 머리카락, 피부가 흐릿하지만, 오른쪽은 개별 머리카락과 피부 질감까지 선명하게 표현됩니다. 또한 그림 ①의 노드 구성 다이어그램과 그림 ②의 실제 출력 해상도 체인(출력 PNG의 측정값인 2688×3456과 일치함)을 참조하십시오. 저는 시각적 비교를 통해서만 차이점을 언급합니다 (정량적 점수는 측정되지 않음). |
솔직하게 말씀드리면: 제가 측정하지 않은 것들
오해를 방지하기 위해, 수치를 인용하지 않는 범위를 명시합니다.
- 생성 시간 (Generation time): 측정되지 않음. 제출 스크립트의 폴링 (polling)은 측정된 시간이 아니라 "3초 × 최대 120회 = 360초"라는 **타임아웃 상한선 (timeout ceiling)**입니다. 따라서 "약 N분"이라고 작성하지 않겠습니다.
- VRAM / 통합 메모리 (unified memory) 소비량: 측정되지 않음 (로그 없음).
- it/s · 샘플링 속도 (sampling speed): 측정되지 않음.
- 단일 단계 (single-shot) 버전과의 정량적 비교 점수: 측정되지 않음. 저는 출력 이미지의 시각적 비교를 통해서만 품질 차이를 언급합니다.
요약: 재현 가능한 핵심 사항
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기