- Published on
PyTorch 2.x Torch-TensorRT 변환 실패 원인·해결
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서론
PyTorch 2.x로 올라오면서 torch.compile(TorchDynamo/FX/AOTAutograd/Inductor) 파이프라인이 기본 최적화 경로가 되었고, 그에 맞춰 Torch-TensorRT도 torch_tensorrt.compile 중심으로 사용 패턴이 바뀌었습니다. 하지만 현장에서는 변환(컴파일) 단계에서 바로 실패하거나, 변환은 되는데 런타임에서 정확도/성능/메모리 문제로 되돌아가는 경우가 흔합니다.
이 글은 “왜 실패하는지”를 로그와 증상으로 빠르게 분류하고, 가장 재현성이 높은 해결책을 단계별로 정리합니다. 특히 PyTorch 2.x 특유의 그래프 캡처/분할, 동적 shape, dtype, 연산자 지원 범위, 버전 호환성 문제를 중심으로 다룹니다.
문제 원인을 좁히는 방식은 다른 장애 진단과 유사합니다. 예를 들어 쿠버네티스에서 OOMKilled를 추적하듯이(메모리/shape 폭증) 원인 후보를 로그로 줄여야 합니다. 필요하면 Kubernetes OOMKilled 진단과 메모리 누수 추적 실전도 함께 참고하면 좋습니다.
변환 파이프라인 한 장으로 이해하기
Torch-TensorRT 변환 실패를 이해하려면 “어디서 실패했는지”를 먼저 분리해야 합니다.
- 그래프 캡처 실패: TorchDynamo가 Python 코드를 그래프로 못 묶음
- FX 그래프 변환 실패: 추적은 됐는데 FX 변환/정규화 단계에서 실패
- TensorRT 빌드 실패: 엔진 빌드(레이어 변환, tactic 선택, 메모리)에서 실패
- 런타임 실패: 엔진은 만들어졌지만 실행 중 오류(입력 shape 불일치, 플러그인 누락)
각 단계는 로그 키워드가 다릅니다.
- 캡처 실패:
torch._dynamo.exc/graph break/Unsupported/Guard - TRT 빌드 실패:
Builder/tactic/workspace/kINVALID_ARGUMENT - 런타임:
enqueueV3/binding/shape mismatch/cuda error
실패 원인 1: 버전 매칭 불일치(가장 흔함)
PyTorch 2.x, CUDA, cuDNN, TensorRT, Torch-TensorRT는 서로 강하게 결합되어 있습니다. 다음 중 하나라도 어긋나면 “설치는 되는데 변환이 실패”하는 케이스가 많습니다.
- PyTorch가 사용하는 CUDA 런타임과 TensorRT가 링크된 CUDA가 다름
- Torch-TensorRT wheel이 특정 PyTorch minor 버전만 지원
- GPU 아키텍처(SM) 미지원 또는 드라이버가 너무 낮음
진단 체크 코드
아래는 최소한의 환경 정보를 한 번에 모으는 스니펫입니다.
import torch
print("torch", torch.__version__)
print("cuda available", torch.cuda.is_available())
print("torch cuda", torch.version.cuda)
print("cudnn", torch.backends.cudnn.version())
print("gpu", torch.cuda.get_device_name(0) if torch.cuda.is_available() else None)
try:
import tensorrt as trt
print("tensorrt", trt.__version__)
except Exception as e:
print("tensorrt import failed", repr(e))
try:
import torch_tensorrt
print("torch_tensorrt", torch_tensorrt.__version__)
except Exception as e:
print("torch_tensorrt import failed", repr(e))
해결 가이드
- 가장 안전한 전략은 “PyTorch와 Torch-TensorRT를 같은 CUDA 계열로 맞춘 사전 빌드 조합”을 쓰는 것입니다.
- 도커를 사용한다면 NVIDIA NGC 계열 이미지 또는 Torch-TensorRT에서 권장하는 베이스 이미지를 고르는 편이 변수를 줄입니다.
- 소스 빌드를 해야 한다면,
CUDA_HOME,LD_LIBRARY_PATH에 서로 다른 CUDA가 섞이지 않도록 정리하세요.
실패 원인 2: TorchDynamo 그래프 캡처 실패(그래프 브레이크)
PyTorch 2.x에서 torch.compile 또는 Torch-TensorRT의 Dynamo 경로는 “그래프로 만들 수 있는 코드”만 최적화할 수 있습니다. 다음 패턴이 있으면 그래프가 끊기거나 아예 실패합니다.
if x.shape[0] > 1:같은 데이터 의존 분기- 리스트/딕셔너리 조작, Python
for루프(텐서가 아닌 객체 기반) - 커스텀 CUDA extension이나 일부
torch.ops호출
증상
- 로그에
graph break가 많이 찍힘 torch._dynamo.exc.Unsupported예외- 변환이 되더라도 “일부만 TRT, 나머지는 PyTorch”로 돌아가 성능이 안 나옴
해결책 1: 변환 대상을 좁혀서 실패 지점을 찾기
모델 전체를 한 번에 변환하려 하지 말고, 서브모듈 단위로 쪼개서 어떤 블록이 문제인지 확인합니다.
import torch
import torch.nn as nn
class M(nn.Module):
def __init__(self):
super().__init__()
self.backbone = nn.Sequential(
nn.Conv2d(3, 16, 3, padding=1),
nn.ReLU(),
nn.Conv2d(16, 16, 3, padding=1),
nn.ReLU(),
)
self.head = nn.AdaptiveAvgPool2d((1, 1))
def forward(self, x):
x = self.backbone(x)
return self.head(x)
model = M().eval().cuda()
example = torch.randn(1, 3, 224, 224, device="cuda")
# 먼저 backbone만 변환 시도
sub = model.backbone
실무에서는 이 방식으로 “문제 연산자/패턴이 있는 블록”을 빠르게 특정할 수 있습니다.
해결책 2: 그래프 친화적으로 코드 리팩터링
- 데이터 의존 분기를 텐서 연산(
torch.where)로 바꾸기 - Python 루프를 벡터화
- 텐서 shape를 Python 정수로 꺼내서 쓰는 패턴 최소화
실패 원인 3: TensorRT 미지원 연산자/패턴
Torch-TensorRT는 모든 PyTorch 연산자를 TensorRT 레이어로 바꾸지 못합니다. 특히 다음이 자주 걸립니다.
- 일부
aten::연산(버전별 지원 차이 큼) einsum, 복잡한 indexing, 특정scatter/gather변형- 동적 shape에서의
reshape/view조합 LayerNorm/GroupNorm의 특정 파라미터 조합(버전에 따라)
해결책 1: 하이브리드 실행(그래프 분할 허용)
전체를 TRT로 못 보내도, 중요한 구간만 TRT로 보내고 나머지는 PyTorch로 두는 전략이 현실적입니다.
import torch
import torch_tensorrt
model = model.eval().cuda()
inputs = [torch_tensorrt.Input(
min_shape=(1, 3, 224, 224),
opt_shape=(4, 3, 224, 224),
max_shape=(8, 3, 224, 224),
dtype=torch.float16,
)]
compiled = torch_tensorrt.compile(
model,
inputs=inputs,
enabled_precisions={torch.float16},
# 핵심: 분할을 허용해 변환 성공률을 올림
require_full_compilation=False,
)
require_full_compilation을 True로 두면 “지원 안 되는 연산자 하나” 때문에 전체가 실패할 수 있습니다. 성능 목표가 “전체 TRT”가 아니라면 우선 성공시키고, 분할된 노드를 로그로 확인해 개선하는 편이 빠릅니다.
해결책 2: 플러그인 또는 대체 연산 사용
- TensorRT 플러그인으로 대체(직접 구현 또는 공개 플러그인 활용)
- PyTorch 레벨에서 다른 연산 조합으로 치환(예: 특정 normalize를 다른 형태로)
실패 원인 4: 동적 shape/입력 스펙 불일치
TensorRT는 shape에 민감합니다. Torch-TensorRT는 입력 스펙을 통해 최적화 프로파일을 만들고 엔진을 빌드하는데, 다음이 흔한 실패 포인트입니다.
- 실제 입력이
min/opt/max범위를 벗어남 - 배치 축을 포함한 차원 순서가 다름(NCHW vs NHWC)
dtype가 다름(float32로 빌드했는데float16입력)
증상
- 변환은 되지만 실행 시
shape mismatch또는 바인딩 오류 - 특정 배치 크기에서만 실패
해결책: 입력 스펙을 “현실적인 범위”로 명시
inputs = [torch_tensorrt.Input(
min_shape=(1, 3, 256, 256),
opt_shape=(8, 3, 512, 512),
max_shape=(16, 3, 1024, 1024),
dtype=torch.float16,
)]
compiled = torch_tensorrt.compile(
model,
inputs=inputs,
enabled_precisions={torch.float16},
)
# 런타임 입력이 max를 넘지 않도록 보장해야 함
x = torch.randn(4, 3, 512, 512, device="cuda", dtype=torch.float16)
with torch.no_grad():
y = compiled(x)
운영 환경에서 입력 크기가 넓게 흔들리면 엔진을 여러 개(해상도/배치 구간별)로 나누는 것이 오히려 안정적입니다.
실패 원인 5: dtype/정밀도 설정 문제(FP16, BF16, INT8)
정밀도는 성능에 직결되지만, 변환 실패의 원인이 되기도 합니다.
- FP16에서 일부 레이어가 오버플로/언더플로로 정확도 급락
- BF16은 조합에 따라 지원이 제한적
- INT8은 calibration 데이터/스케일 설정이 필요
해결책: 우선 FP32로 성공시키고 단계적으로 내리기
compiled_fp32 = torch_tensorrt.compile(
model,
inputs=[torch_tensorrt.Input((1, 3, 224, 224), dtype=torch.float32)],
enabled_precisions={torch.float32},
)
compiled_fp16 = torch_tensorrt.compile(
model,
inputs=[torch_tensorrt.Input((1, 3, 224, 224), dtype=torch.float16)],
enabled_precisions={torch.float16},
)
정확도 이슈가 의심되면 “민감 레이어만 FP32로 남기기” 같은 mixed precision 전략을 검토해야 합니다(지원 범위는 버전에 따라 다르므로 릴리즈 노트를 확인).
실패 원인 6: Workspace/메모리 부족, tactic 선택 실패
엔진 빌드 중 workspace(TensorRT builder가 tactic 탐색에 쓰는 메모리)가 부족하면 tactic을 못 찾아 실패하거나, 성능이 나쁜 tactic으로 고정될 수 있습니다.
증상
out of memory또는 tactic 관련 경고/에러- 큰 입력에서만 빌드 실패
해결책
- 입력
max_shape를 과도하게 키우지 않기 - 가능한 경우 더 큰 GPU에서 엔진을 빌드하거나, 빌드와 서빙을 분리
- 모델을 채널 수/해상도 관점에서 경량화
이 문제는 운영에서 OOMKilled처럼 터지기도 하므로, 리소스 상한과 입력 분포를 먼저 확인하는 것이 좋습니다. (관련 진단 관점은 Kubernetes OOMKilled 진단과 메모리 누수 추적 실전과 유사합니다.)
실패 원인 7: 전처리/후처리 포함으로 그래프가 깨짐
실무 모델은 종종 forward 안에 전처리(리사이즈, 패딩, 정규화)나 후처리(NMS, topk, decode)가 섞여 있습니다. 이 부분이 변환 실패의 핵심 원인이 되곤 합니다.
해결책: 모델과 파이프라인을 분리
- TRT로 보내고 싶은 “순수 NN” 부분만 모듈로 분리
- 전/후처리는 PyTorch 또는 별도 CUDA 커널로 처리
class Wrapped(nn.Module):
def __init__(self, core):
super().__init__()
self.core = core
def forward(self, x):
# 전처리(예: 정규화) - 필요하면 밖으로 빼기
x = x.contiguous()
y = self.core(x)
return y
wrapped = Wrapped(model).eval().cuda()
로그 기반 트러블슈팅 체크리스트(권장 순서)
- 환경 버전 출력: PyTorch/CUDA/TensorRT/Torch-TensorRT 버전과 GPU 확인
- FP32 + 고정 shape로 최소 예제 성공시키기
- 실패 시:
graph break면 모델 코드를 그래프 친화적으로 리팩터링Unsupported aten::이면 분할 허용 또는 연산 치환/플러그인shape mismatch면 입력 스펙(min/opt/max) 재설계tactic/workspace면 max shape 축소, 빌드 리소스 재검토
- 성공 후 FP16/INT8로 단계적 최적화
이 접근은 네트워크 장애에서 타임아웃 원인을 좁히는 방식과도 비슷합니다. 시스템 전체를 한 번에 의심하기보다, 관측 가능한 지표로 범위를 줄이는 식입니다. 쿠버네티스/인그레스 타임아웃처럼 “증상은 502인데 원인은 다양”한 케이스를 다룬 글로는 EKS ALB Ingress 502 target timeout 원인·해결도 참고할 만합니다.
실전 예제: 변환 실패를 재현하고 해결하기
아래 예제는 동적 shape와 FP16을 사용하면서도 변환 성공률을 높이는 템플릿입니다.
import torch
import torch.nn as nn
import torch_tensorrt
class Net(nn.Module):
def __init__(self):
super().__init__()
self.net = nn.Sequential(
nn.Conv2d(3, 32, 3, padding=1),
nn.ReLU(),
nn.Conv2d(32, 64, 3, padding=1),
nn.ReLU(),
nn.AdaptiveAvgPool2d((1, 1)),
nn.Flatten(),
nn.Linear(64, 10),
)
def forward(self, x):
return self.net(x)
model = Net().eval().cuda().half()
inputs = [torch_tensorrt.Input(
min_shape=(1, 3, 128, 128),
opt_shape=(8, 3, 224, 224),
max_shape=(16, 3, 512, 512),
dtype=torch.float16,
)]
compiled = torch_tensorrt.compile(
model,
inputs=inputs,
enabled_precisions={torch.float16},
require_full_compilation=False,
)
x = torch.randn(4, 3, 224, 224, device="cuda", dtype=torch.float16)
with torch.no_grad():
y = compiled(x)
print(y.shape)
여기서도 실패한다면, 다음을 우선 확인하세요.
model이 정말eval()인지(드롭아웃/배치정규화 동작 차이)- 입력 텐서가
contiguous()인지(일부 뷰 텐서에서 문제가 나는 경우) - 입력 dtype과
enabled_precisions가 일치하는지
결론
PyTorch 2.x에서 Torch-TensorRT 변환 실패는 대부분 다음 4가지로 수렴합니다.
- 버전/라이브러리 매칭 불일치
- 그래프 캡처 실패(그래프 브레이크)
- TensorRT 미지원 연산자
- 동적 shape 및 dtype/프로파일 설계 문제
해결의 핵심은 “전체를 한 번에 바꾸려 하지 말고” FP32 고정 shape 최소 예제로 성공시키고, 로그로 실패 지점을 분리한 뒤, 입력 스펙과 분할 전략을 조정하는 것입니다. 이 과정을 거치면 변환 성공률뿐 아니라 운영 안정성(특정 입력에서만 터지는 런타임 오류)까지 함께 개선됩니다.