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 평균 | 판정 |
|---|---|---|---|---|
| ColorPreprocessor | 0.224 s | 0% | 0.0% | CPU |
| CannyEdgePreprocessor | 0.227 s | 0% | 0.0% | CPU |
| Scribble_XDoG | 0.329 s | 0% | 0.0% | CPU |
| TEEDPreprocessor | 0.333 s | 91% | 11.1% | GPU |
| LineartStandard | 0.434 s | 12% | 1.3% | CPU |
| PiDiNetPreprocessor | 0.546 s | 100% | 42.4% | GPU |
| DWPreprocessor (DWPose) | 0.648 s | 8% | 3.2% | CPU |
| HEDPreprocessor | 0.747 s | 100% | 50.6% | GPU |
| AnimeLineArt | 1.237 s | 100% | 18.7% | GPU |
| MiDaS-DepthMap | 2.120 s | 100% | 33.4% | GPU |
| BAE-NormalMap | 2.449 s | 100% | 46.0% | GPU |
| OpenposePreprocessor | 5.593 s | 100% | 5.7% | GPU(부분) |
| DepthAnythingV2 | 8.121 s | 100% | 22.4% | GPU |
| LineArtPreprocessor | 2.110 s | 100% | 23.4% | GPU · 아래 참고 |
맨 위 네 개 중 Color·Canny·XDoG가 정확히 0%인 게 측정이 맞다는 근거입니다. 이것들은 OpenCV 알고리즘이라 GPU를 쓸 이유가 없고, 실제로 안 씁니다. 그리고 제일 빠릅니다. 구도만 잡으면 되는 자리에 깊이 모델을 쓸 이유가 없다는 뜻이기도 하고요.
DWPose는 GPU를 안 씁니다
포즈 추정치고 0.65초면 빠른 편이라 그냥 넘길 뻔했는데, 사용률이 이상하게 낮아서 따로 봤습니다. 웜 상태로 세 번 더 돌린 값입니다.
| 회차 | 시간 | 사용률 최대 | 평균 | 30% 초과 표본 |
|---|---|---|---|---|
| #2 | 0.644 s | 3% | 3.0% | 0 / 30 |
| #3 | 0.643 s | 3% | 3.0% | 0 / 30 |
| #4 | 0.656 s | 8% | 3.2% | 0 / 31 |
| HED (대조) | 0.734 s | 100% | 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 카드라면 첫 실행부터 안 될 텐데, 그런 경험 있으시면 알려주세요.