Published on

PyTorch 2.x Torch-TensorRT 변환 실패 원인·해결

Authors

서론

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 변환 실패를 이해하려면 “어디서 실패했는지”를 먼저 분리해야 합니다.

  1. 그래프 캡처 실패: TorchDynamo가 Python 코드를 그래프로 못 묶음
  2. FX 그래프 변환 실패: 추적은 됐는데 FX 변환/정규화 단계에서 실패
  3. TensorRT 빌드 실패: 엔진 빌드(레이어 변환, tactic 선택, 메모리)에서 실패
  4. 런타임 실패: 엔진은 만들어졌지만 실행 중 오류(입력 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_compilationTrue로 두면 “지원 안 되는 연산자 하나” 때문에 전체가 실패할 수 있습니다. 성능 목표가 “전체 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()

로그 기반 트러블슈팅 체크리스트(권장 순서)

  1. 환경 버전 출력: PyTorch/CUDA/TensorRT/Torch-TensorRT 버전과 GPU 확인
  2. FP32 + 고정 shape로 최소 예제 성공시키기
  3. 실패 시:
    • graph break면 모델 코드를 그래프 친화적으로 리팩터링
    • Unsupported aten::이면 분할 허용 또는 연산 치환/플러그인
    • shape mismatch면 입력 스펙(min/opt/max) 재설계
    • tactic/workspace면 max shape 축소, 빌드 리소스 재검토
  4. 성공 후 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 최소 예제로 성공시키고, 로그로 실패 지점을 분리한 뒤, 입력 스펙과 분할 전략을 조정하는 것입니다. 이 과정을 거치면 변환 성공률뿐 아니라 운영 안정성(특정 입력에서만 터지는 런타임 오류)까지 함께 개선됩니다.