ControlNet 워크플로우를 짜다 보면 전처리기를 뭘 쓸지 고르게 되는데, 결과물 모양만 보고 고르지 속도나 자원은 잘 안 봅니다. 저도 그랬다가 어떤 조합에서 유독 느린 게 걸려서 재봤습니다. 차이가 생각보다 큽니다.

결론

  • 같은 힌트 한 장 뽑는 데 0.22초부터 8.1초까지, 36배 벌어집니다.
  • DWPose는 이름값과 달리 GPU를 거의 안 씁니다. 웜 상태에서 표본 91개 중 사용률 30%를 넘은 게 하나도 없었습니다.
  • LineArt는 한 번 돌리면 20GB를 잡고 안 놓습니다. 그래서 연달아 두 번 돌리면 두 번째가 OOM으로 죽습니다. 32GB 카드에서요.
  • Canny·Color 같은 순수 알고리즘 계열은 원래 CPU입니다. 느린 게 아니라 그게 정상이고, 오히려 제일 빠릅니다.

환경과 재는 법

R9700(gfx1201, 31.86GiB), ROCm 7.2.4, ComfyUI 0.28.0, comfyui_controlnet_aux. 입력은 1216×832 이미지 한 장, resolution 1024 고정.

처음엔 VRAM 증가로 GPU 사용을 가르려 했는데 그게 안 됩니다. torch 캐싱 할당자가 해제한 블록을 계속 들고 있어서, 어느 노드 차례에 풀이 확장되면 그 노드가 20GB를 쓴 것처럼 찍힙니다. 그래서 /sys/class/drm/card0/device/gpu_busy_percent를 20ms 간격으로 샘플링했습니다. 메모리 회계와 달리 할당자 동작에 오염되지 않습니다.

ComfyUI는 같은 그래프를 두 번 보내면 실행 캐시로 돌려주므로, 회차마다 resolution을 1픽셀씩 낮춰 캐시를 깼습니다. 이걸 안 하면 전부 0.22초로 나옵니다 — 처음에 그렇게 재고 표까지 만들었다가 버렸습니다.

전체 결과

중앙값 기준이고, 모델 다운로드·로드가 끝난 뒤의 순수 실행 시간입니다.

전처리기시간GPU 최대GPU 평균판정
ColorPreprocessor0.224 s0%0.0%CPU
CannyEdgePreprocessor0.227 s0%0.0%CPU
Scribble_XDoG0.329 s0%0.0%CPU
TEEDPreprocessor0.333 s91%11.1%GPU
LineartStandard0.434 s12%1.3%CPU
PiDiNetPreprocessor0.546 s100%42.4%GPU
DWPreprocessor (DWPose)0.648 s8%3.2%CPU
HEDPreprocessor0.747 s100%50.6%GPU
AnimeLineArt1.237 s100%18.7%GPU
MiDaS-DepthMap2.120 s100%33.4%GPU
BAE-NormalMap2.449 s100%46.0%GPU
OpenposePreprocessor5.593 s100%5.7%GPU(부분)
DepthAnythingV28.121 s100%22.4%GPU
LineArtPreprocessor2.110 s100%23.4%GPU · 아래 참고

맨 위 네 개 중 Color·Canny·XDoG가 정확히 0%인 게 측정이 맞다는 근거입니다. 이것들은 OpenCV 알고리즘이라 GPU를 쓸 이유가 없고, 실제로 안 씁니다. 그리고 제일 빠릅니다. 구도만 잡으면 되는 자리에 깊이 모델을 쓸 이유가 없다는 뜻이기도 하고요.

DWPose는 GPU를 안 씁니다

포즈 추정치고 0.65초면 빠른 편이라 그냥 넘길 뻔했는데, 사용률이 이상하게 낮아서 따로 봤습니다. 웜 상태로 세 번 더 돌린 값입니다.

회차시간사용률 최대평균30% 초과 표본
#20.644 s3%3.0%0 / 30
#30.643 s3%3.0%0 / 30
#40.656 s8%3.2%0 / 31
HED (대조)0.734 s100%52.2%19 / 35

91개 표본 중 30%를 넘은 게 하나도 없습니다. 같은 시간대에 도는 HED는 절반 넘게 30%를 넘고요. DWPose는 onnxruntime으로 도는데, ROCm 실행 프로바이더가 안 잡히면 조용히 CPU로 떨어집니다. 에러가 안 나니 알아채기 어렵습니다.

지금 속도로도 쓸 만하긴 합니다. 다만 GPU를 늘려도 이건 안 빨라진다는 뜻이고, 배치로 많이 돌릴 계획이면 CPU 코어 수가 병목이 됩니다.

LineArt는 두 번째에 죽습니다

이게 제일 골치아픈 발견입니다. 깨끗한 상태에서 시작해 연달아 두 번 돌린 결과입니다.

#1  2.110s 성공   VRAM 피크 +20,532 MiB   사용률 최대 100%
#2  실패 — CUDA out of memory. Tried to allocate 18.38 GiB.
           GPU 0 has a total capacity of 31.86 GiB of which 11.78 GiB is free.

라인아트 한 장 뽑는 데 18GB를 요구합니다. 그리고 첫 실행이 잡은 20GB가 돌아오지 않아서 두 번째에는 자리가 없습니다. 32GB 카드에서 전처리기 하나 때문에 워크플로우가 죽습니다.

VRAM이 왜 안 돌아오는지는 따로 정리해둔 글에 있습니다. 요약하면 PYTORCH_HIP_ALLOC_CONF=expandable_segments:True 때문인데, 그걸 끄면 큰 작업 뒤에도 VRAM이 반납됩니다. 이 경우에도 도움이 될 가능성이 높지만 조합해서 재보지는 않았습니다.

당장은 이렇게 피하시면 됩니다 — LineArt를 쓰는 워크플로우를 연달아 돌리지 말고, resolution을 낮추거나, 결과가 비슷하면 AnimeLineArt(1.24초, 문제 없음)나 LineartStandard(0.43초, CPU)로 바꾸세요.

Openpose는 반만 GPU입니다

5.6초로 느린 편인데 사용률 평균이 5.7%입니다. 최대는 100%를 찍고요. 잠깐 GPU를 쓰고 나머지 대부분은 CPU 후처리에 쓴다는 얘기입니다. DWPose가 같은 일을 0.65초에 하니, 포즈가 필요하면 DWPose 쪽이 8배 이상 빠릅니다. 다만 위에 적었듯 그쪽은 CPU로 돕니다.

한계

  • 카드 하나, ROCm 하나입니다. NVIDIA에서는 DWPose가 GPU로 돌 가능성이 높습니다 (onnxruntime CUDA 프로바이더). 그 경우 순위가 달라집니다.
  • 입력 이미지 한 장, 해상도 하나입니다. 해상도를 올리면 GPU 쪽이 더 유리해질 수 있습니다.
  • 사용률 샘플링은 20ms 간격이라, 0.22초짜리 노드는 표본이 10개 남짓입니다. 짧은 GPU 사용을 놓쳤을 수 있습니다. DWPose 표에는 표본 수를 같이 적어뒀습니다.
  • 14종만 봤습니다. 설치된 전처리기는 64종입니다.

같이 재주실 분

NVIDIA에서 DWPose 사용률이 궁금합니다. 워크플로우에 전처리기 하나만 놓고 돌리면서 nvidia-smi dmon -s u로 보시면 바로 나옵니다. 여기서처럼 3% 근처면 그쪽도 CPU고, 높게 나오면 ROCm 쪽만의 문제입니다.

LineArt가 다른 카드에서도 18GB를 요구하는지도 궁금합니다. 24GB 카드라면 첫 실행부터 안 될 텐데, 그런 경험 있으시면 알려주세요.