상태: 초안 (2026-10-16), 일부 실측. torch.compile·메모리 배치·cuDNN 설정은 집 랩의 8GB급 노트북용 GPU 한 장에서 1회용 파드로 쟀습니다(PyTorch 2.9, 예열 20회 뒤 50회 × 5묶음의 중앙값). 융합의 원리는 노트북 CPU 예제로 확인했습니다(
code/03_fusion.py, 몇 초). ONNX Runtime 과 TensorRT 는 실험 이미지에 없어 미실측입니다(내려받지 않았습니다). 시리즈 「비전 모델 읽기 3」 3편. 정밀도 줄이기는 2편에 있습니다.
목차
- 1. 직관: 요리사와 냉장고
- 2. 직접 해 보기(노트북 CPU)
- 3. 그래프 컴파일러는 무엇을 하나
- 4. ONNX 와 런타임: 모델을 들고 다니는 형식
- 5. 우리 실측: 공 모델에 torch.compile
- 6. 한계
- 7. 우리 프로젝트와의 연결
- 참고 문헌
1. 직관: 요리사와 냉장고
GPU 의 계산 장치는 빠르지만, 데이터는 칩 밖의 메모리(냉장고)에 있습니다. 즉시 실행(eager)은 「합성곱 → 배치 정규화 → ReLU → 더하기」 를 각각 따로 합니다. 합성곱 결과를 냉장고에 넣고, 배치 정규화가 꺼내 고치고 다시 넣고, ReLU 가 또 꺼내고 넣습니다. 큰 특징 지도라면 계산보다 넣고 꺼내는 시간이 깁니다. 연산 융합은 「꺼낸 김에 넷을 다 하고 한 번만 넣는」 것입니다.
융합에는 두 종류가 있습니다.
- 값을 미리 접기: 추론 때 배치 정규화(BN)는 채널마다 고정된 곱하기·더하기라, 앞의 합성곱 가중치와 편향에 미리 넣을 수 있습니다. 연산이 아예 사라집니다.
- 커널 묶기: ReLU·더하기·형식 바꾸기처럼 원소마다 따로 하는 연산을 앞뒤 커널과 한 커널로 묶습니다.
BN 접기: y = γ·(W∗x + b − μ)/√(σ²+ε) + β
= (γ/√(σ²+ε))·W ∗ x + (γ·(b − μ)/√(σ²+ε) + β)
= W' ∗ x + b' ← 합성곱 하나로
2. 직접 해 보기(노트북 CPU)
code/03_fusion.py 를 노트북 CPU 스레드 하나로 돌렸습니다(7회 중앙값).
| 실험 | 결과 |
|---|---|
| 합성곱(32→64, 3×3, 입력 144×256) + BN 을 접기 | 최대 차이 4.77e-06(FP32 반올림 수준). 시간 5.80 → 5.80ms(차이 없음) |
| 원소별 사슬 relu(x·1.5 + 0.5)·0.9, 원소 1,677만 개 | 즉시 실행 9.7ms → torch.compile 2.4ms(4.1배), 결과 차이 0 |
| 같은 사슬을 손으로 제자리 연산 | 7.4ms |
| 원소 102만 개 더하기를 1,000번 나눠 부르기 | 0.13 → 0.88ms, 부를 때마다 약 0.8µs |
- BN 접기는 수학적으로 같은 결과를 냈지만 이 CPU 에서는 시간이 줄지 않았습니다. 큰 합성곱 옆에서 BN 한 번은 작은 몫이었습니다(첫 실행에서는 2.6% 줄었고, 두 번째에는 0% — 잡음 수준).
- 원소별 사슬은 다릅니다. 즉시 실행은 큰 배열을 네 번 읽고 쓰는데, 컴파일된 판은 한 번 읽고 한 번 씁니다. 계산은 같고 메모리 왕복만 줄었는데 4배 빨라졌습니다. 이 기계에서 첫 컴파일은 캐시가 없을 때 5.4초, 캐시가 있을 때 0.6초였습니다.
- 손으로 제자리 연산을 써도 7.4ms 에 그쳤습니다. 새 배열 할당은 줄였지만 여전히 네 번 읽고 씁니다. 사람이 파이썬 수준에서 할 수 있는 최적화에는 한계가 있고, 그 아래 한 층(커널)을 바꿔야 크게 줄어듭니다.
3. 그래프 컴파일러는 무엇을 하나
TVM(Chen 외, 2018) 은 모델을 그래프로 받아 연산을 넷으로 나눕니다. 원소마다 하는 일(injective), 합치는 일(reduction), 출력에 원소별 일을 붙일 수 있는 복잡한 일(합성곱 같은 complex-out-fusable), 그리고 묶을 수 없는 일. 규칙에 따라 앞의 셋을 묶습니다. 연산 융합만으로 1.2–2배 빨라졌고(2015년 데스크톱급 GPU, 합성곱+BN+ReLU 등 작업 4개, 3절·그림 4), 다른 프레임워크 대비 끝단 1.6–3.8배였습니다(6.1절).
PyTorch 2(Ansel 외, 2024) 의 torch.compile 은 두 부분입니다.
- TorchDynamo: 파이썬 바이트코드를 가로채 연산 그래프를 잡습니다. 그래프로 만들 수 없는 파이썬 코드가 나오면 그 앞뒤를 끊어 나머지는 그대로 돌립니다. 그래프 잡기 오버헤드는 추론에서 약 5% 였습니다(표 2, 데이터센터 GPU).
- TorchInductor: 잡은 그래프를 융합해 GPU 용 Triton 커널(또는 CPU 용 C++)을 만듭니다.
데이터센터 GPU 에서 TIMM 이미지 모델 62개의 추론이 즉시 실행보다 FP32 2.48배, FP16 2.77배 빨랐습니다(표 3). 융합과 인라인을 둘 다 끄면 오히려 0.80배로 즉시 실행보다 느려졌습니다(표 4, HuggingFace FP16 추론). 속도의 대부분이 융합에서 온다는 뜻입니다. 초록의 「추론 2.27배」 는 어느 조합의 평균인지 본문에서 확인하지 못해 여기서는 표 3·4 의 값만 씁니다.
4. ONNX 와 런타임: 모델을 들고 다니는 형식
torch.compile 은 PyTorch 안에서 돕니다. 서버에 파이썬과 PyTorch 를 두고 싶지 않거나, 브라우저·폰에서 돌려야 하면 모델을 표준 그래프 파일로 꺼내야 합니다.
- ONNX: 연산 종류와 그래프를 정한 공개 형식입니다. PyTorch 에서 내보내면 다른 런타임이 읽습니다.
- ONNX Runtime: ONNX 를 실행하는 런타임입니다. 그래프 최적화를 세 단계로 합니다. 기본(상수 접기, 합성곱+BN 같은 중복 제거), 확장(GELU·어텐션 같은 복잡한 융합, CPU·CUDA 등), 배치(메모리 배치를 CPU 에 맞게 바꿈). 기본 설정은 전부 켭니다(공식 문서). 실제 계산은 실행 공급자가 합니다 — CPU, CUDA, TensorRT, Core ML, 브라우저의 WebGPU·WASM 등. 같은 파일을 여러 기기에 들고 다니는 것이 ONNX 의 값입니다(7편).
- TensorRT: NVIDIA GPU 전용 최적화기입니다. 층을 융합하고, 커널 후보를 실제로 돌려 보고 빠른 것을 고르고, FP16·INT8 로 낮춥니다. 결과는 그 GPU 에 맞춘 「엔진」 파일이라 다른 GPU 로 옮기면 다시 만들어야 합니다. 예전에는 보정 데이터로 INT8 범위를 정하는 「보정기」(엔트로피·최소최대)를 썼는데, 공식 문서 기준 TensorRT 10.1 부터 보정기는 지원 중단이고 모델 안에 양자화·역양자화 노드를 넣는 명시적 방식으로 바뀌었습니다.
5. 우리 실측: 공 모델에 torch.compile
TT Score 의 공 모델(BlurBall, 3프레임 창, 512×288, 배치 1)을 실험용 GPU 에서 쟀습니다.
| 실행 방식 | FP32 | FP16 | 첫 부름(컴파일) |
|---|---|---|---|
| 즉시 실행(지금 운영 방식) | 15.09ms | 9.77ms | — |
| 즉시 실행, cuDNN 자동 고르기 끔 | 15.08ms | 9.75ms | — |
| 즉시 실행, channels_last(NHWC) | 18.62ms | 9.02ms | — |
| torch.compile 기본 | 12.91ms(1.17배) | 4.61ms(2.12배) | 10–11초 |
| torch.compile reduce-overhead(CUDA 그래프) | 12.55ms | 4.34ms(2.25배) | 7–8초 |
| torch.compile max-autotune(CUDA 그래프 없이) | 12.79ms | 4.48ms | 41–46초 |
(FP16 은 autocast. 배수는 같은 정밀도의 즉시 실행 대비.)
같은 실행을 프로파일러로 보니 즉시 실행 FP32 한 번에 CUDA 커널 478개가 불렸고, GPU 가 일한 시간 14.88ms 중 합성곱이 8.09ms(72번), 배치 정규화가 3.27ms(71번, 22%)였습니다. 커널 시간의 합이 벽시계 시간(15.09ms)과 거의 같으므로 GPU 는 쉬지 않았습니다. 즉 부르기 비용이 아니라 메모리를 오가는 원소별 연산이 문제였고, 컴파일러가 이것을 묶어 없앤 것으로 해석합니다(컴파일된 판의 커널 수는 세지 않았습니다).
읽는 법입니다.
- FP16 에서 더 크게 이겼습니다. FP16 즉시 실행은 합성곱은 빨라졌지만 BN·ReLU·형식 바꾸기가 그대로 남아 몫이 커졌고, 컴파일러가 바로 그것을 지웠습니다. 2편의 정밀도 줄이기와 3편의 융합은 서로 곱해집니다(15.09 → 4.34ms, 3.5배).
- CUDA 그래프(reduce-overhead)는 조금 더 줄였습니다. 한 번 실행의 커널들을 그래프로 묶어 한 번에 GPU 에 넘기므로, 커널마다 드는 파이썬·드라이버의 부르기 비용이 사라집니다. 이 모델은 GPU 가 이미 바빠서 이득이 작았습니다(4.61 → 4.34ms).
- 오래 고른다고 더 빠르지 않았습니다. max-autotune 은 45초를 들이고 기본보다 빠르지 않았습니다. 이 GPU 는 행렬곱 자동 고르기를 쓰기엔 계산 단위가 적다는 경고도 나왔습니다.
- 메모리 배치(NHWC)는 FP16 에서만 이득(9.77 → 9.02ms)이고 FP32 에서는 손해였습니다(15.09 → 18.62ms). cuDNN 자동 고르기는 차이가 없었습니다. 「이 설정이 빠르다」 는 일반론보다 그 모델·그 정밀도에서 재 보는 쪽이 정확합니다.
6. 한계
- 첫 부름이 깁니다. 기본 모드도 10초입니다. 실시간 작업자는 뜰 때 예열하므로 감출 수 있지만, 입력 모양(배치 크기)이 바뀌면 다시 컴파일될 수 있습니다. 배치 1·2·3 을 모두 예열해야 합니다(6편). 컴파일 캐시를 디스크에 남겨 재시작을 줄이는 것은 시험하지 않았습니다(미실측).
- 결과가 같은지 따로 봐야 합니다. 융합은 더하는 순서를 바꾸므로 마지막 자리가 달라질 수 있습니다. 공 위치가 같은지는 이번에 재지 않았습니다(미실측). 2편의 FP16 비교처럼 정답 프레임으로 확인하고 켜야 합니다.
- ONNX Runtime·TensorRT 는 재지 못했습니다. 실험 이미지에 없었고, 이번 작업에서 새 패키지를 내려받지 않았습니다. TensorRT 의 FP16·INT8 엔진이 torch.compile 보다 빠를지는 이 글에서 답하지 않습니다.
- 한 GPU, 한 모델의 결과입니다. 트랜스포머처럼 큰 행렬곱이 중심인 모델은 융합의 몫이 다를 수 있습니다.
7. 우리 프로젝트와의 연결
- 운영 작업자는 지금 즉시 실행 FP32 입니다. 5절대로라면 FP16 + 컴파일로 창 하나가 15ms → 4–5ms 가 되어, 같은 GPU 로 세션을 더 받거나 다른 작업과 나눠 쓸 때 여유가 생깁니다. 켜기 전에 할 일은 둘입니다. 녹화 경기의 정답 프레임으로 공 위치·판정이 같은지 확인(2편 방식), 그리고 운영 GPU 에서 다시 재기(GPU 가 바뀌면 득실이 바뀝니다 — 6편).
- 작업자는 뜰 때 배치 1·2·3 을 예열합니다. 컴파일을 켜면 그 예열이 곧 컴파일이 되므로 준비 시간이 10초 단위로 늘어납니다. 준비 확인(
/healthz)이 이미 예열 뒤에 열리므로 사용자는 그 시간을 보지 않습니다. - 폰·브라우저에서 돌릴 모델은 ONNX 로 꺼내는 길이 먼저입니다(7편).
티티 한마디. 똑같은 요리를 냉장고 문 덜 열고 했더니 두 배 빨라졌어요. 재료(가중치)도 레시피(모델)도 그대로인데요!
참고 문헌
- Chen, T. et al. (2018). TVM: An Automated End-to-End Optimizing Compiler for Deep Learning. OSDI 2018. https://arxiv.org/abs/1802.04799
- Ansel, J. et al. (2024). PyTorch 2: Faster Machine Learning Through Dynamic Python Bytecode Transformation and Graph Compilation. ASPLOS 2024. https://doi.org/10.1145/3620665.3640366
- ONNX. https://onnx.ai/ · ONNX Runtime 그래프 최적화. https://onnxruntime.ai/docs/performance/model-optimizations/graph-optimizations.html
- NVIDIA TensorRT 문서(How TensorRT Works, Explicit Quantization). https://docs.nvidia.com/deeplearning/tensorrt/latest/architecture/how-trt-works.html
- 우리 측정: 이 시리즈의 실측(원자료는 저장소 밖 연구 기록)
- 예제 코드와 출력: 이 폴더의
code/03_fusion.py,code/03_fusion.out.txt