AEGLOSLAB 엔비디아 기술 스택

제5부 추론 최적화와 서빙

5.3 TensorRT II — 빌더 최적화·동적 shape

5.3.1 이 장에서 배우는 것 / 선수 지식

5.2에서 ONNX→엔진 빌드의 큰 흐름을 파악하고, optimization profile(min/opt/max)을 맛만 봤다. 이 장은 그 동적 shape를 전면으로 끌어와, 하나의 엔진으로 가변 배치·가변 해상도·가변 시퀀스를 서빙하는 법과, 빌더 최적화 손잡이(optimization level·tactic source·timing cache·builder flag)를 실습으로 깊게 판다.

왜 동적 shape가 추론의 핵심인가? 5.1에서 봤듯 추론 부하는 내가 통제하지 못한다 — 사용자가 배치 1을 보낼 수도, 32를 보낼 수도, 이미지가 224²일 수도 640²일 수도 있다. 모든 형상마다 엔진을 따로 빌드·적재하면 메모리·운영이 폭발한다. 동적 shape는 형상의 범위(min~max)를 하나의 엔진에 담아, 런타임에 실제 형상을 골라 실행한다. 그 대가는 "범위가 넓을수록 각 형상의 최적화가 무뎌진다"는 것 — 이 균형을 optimization profile로 설정하는 것이 이 장의 본질이다.

배우는 것:

  • 정적 vs 동적 shape, explicit batch, 그리고 동적 차원(-1)이 빌드·실행에 미치는 영향.
  • optimization profile(min/opt/max)의 의미 — 빌더가 opt 형상에 맞춰 tactic을 고르고, min~max 범위에서 동작을 보장하는 구조.
  • 다중 프로파일 — 형상 분포가 갈릴 때 여러 프로파일을 두고 런타임에 선택.
  • shape tensor vs execution tensor(형상 자체가 입력이 되는 경우)와 그 함정.
  • 빌더 손잡이 — optimization level·tactic source·timing cache·builder flag(TF32·FP16·sparse·refit 등) — 의 효과.
  • 프로파일을 실제 입력 분포에 맞추는 튜닝과, trtexec/polygraphy로 동적 엔진을 검증·프로파일하기(실습).

선수 지식: 5.2(빌드 흐름·trtexec·Python API·polygraphy — 이 장은 그 위에 쌓는다), 5.1(배치=마스터 노브), 2.3(실행 구성·형상이 성능에 미치는 영향), 1.13~1.14(정밀도 — builder flag). 환경은 5.2과 동일(NGC TensorRT 컨테이너 권장).

정직: 동적 shape API(set_shape·set_input_shape·프로파일 선택 메서드)와 builder flag 이름은 TRT 버전 의존이다(메서드 개명·deprecation 잦음). 흐름은 버전 불변이나 정확한 시그니처는 당신 버전 문서로 확인("시점 의존, 확인 필요"). 성능 수치는 모두 환경 의존 개략값 — 실측.

5.3.2 정적 vs 동적 shape — 무엇이·왜

구분정적 shape 엔진동적 shape 엔진
입력 형상고정차원 일부가 동적(-1)
예input=8x3x224x224input=(-1)x3x(-1)x(-1)
동작 범위그 형상에만 최적·동작min~max 범위에서 동작
여러 배치다른 배치 → 다른 엔진하나의 엔진이 여러 형상 처리
장형상이 고정되어 최적화 최대가변 부하를 하나로 서빙(운영↓)
단형상마다 엔진 폭발범위가 넓을수록 형상별 최적화 무뎌짐

explicit batch(명시적 배치). ONNX 경로는 항상 배치 차원을 그래프에 명시한다(5.2의 EXPLICIT_BATCH). 즉 배치도 다른 차원과 똑같이 동적/정적으로 다룰 수 있는 일반 차원이다. (과거 implicit batch는 deprecated — 신경 쓰지 말 것.)

동적 차원의 표기. ONNX export에서 dynamic_axes로 지정한 축이 엔진에서 동적(-1)이 된다(5.2.4). 흔히 동적으로 두는 축:

  • 배치(N) — 거의 항상. 가변 동시 요청·동적 배칭(5.13)의 전제.
  • 시퀀스 길이(L) — NLP·LLM(가변 입력 길이). (LLM은 TRT-LLM이 따로, 5.7.)
  • 해상도(H·W) — 검출·세그멘테이션(다양한 입력 크기).

핵심 트레이드오프: 동적 범위는 무비용이 아니다. 빌더는 범위 전체에서 정확히 동작하는 커널을 골라야 하므로, 너무 넓은 min~max는 특정 형상의 최고 성능을 저하시킬 수 있다. 실제로 들어올 형상의 분포에 범위를 맞추는 것이 성능의 핵심 요인(5.3.5).

5.3.3 optimization profile — min/opt/max의 작동

동적 엔진을 빌드하려면 각 동적 입력에 optimization profile을 줘야 한다. 세 형상을 지정한다:

   profile.set_shape("input",
       min = (1, 3, 224, 224),     # 허용 최소 — 이보다 작으면 실행 불가
       opt = (8, 3, 224, 224),     # 빌더가 이 형상에 맞춰 tactic을 고름(가장 빠르게 됨)
       max = (32, 3, 224, 224))    # 허용 최대 — 이보다 크면 실행 불가

세 형상의 역할:

형상의미잘못 잡으면
min런타임 입력의 하한더 작은 형상은 실행 거부(런타임 에러)
opt빌더가 오토튜닝(tactic 선택) 기준으로 삼는 형상실제 흔한 형상과 다르면 그 형상에서 비최적
max런타임 입력의 상한 + 메모리 할당 기준너무 크면 활성화 메모리 과할당, 너무 작으면 큰 입력 거부
빌드 시점 런타임 엔진에 고정 opt 형상으로 각 레이어 tactic을 실측·선택 min~max 전 범위에서 동작하도록 커널 제약 ctx.set_input_shape('input', 실제형상) (min~max 안이어야 함) opt에 가까운 형상일수록 빠름, 멀수록 비최적일 수 있음

메모리. TensorRT는 보통 max 형상 기준으로 활성화 메모리를 할당한다(런타임에 형상이 바뀌어도 재할당 없이 동작하도록). 그래서 max를 실제 필요보다 크게 잡으면 메모리 낭비다 — 동시 요청 수(5.1.7)에 직접 영향.

5.3.4 워크스루 (1) — 동적 엔진 빌드·실행

5.2의 ResNet-50을 배치 동적으로 빌드해 여러 배치로 실행한다.

5.3.4.1 trtexec로 동적 엔진 빌드

# min/opt/max를 주면 동적 배치 엔진. opt를 '가장 흔한 배치'로.
trtexec \
  --onnx=resnet50.onnx \
  --saveEngine=resnet50_dyn.plan \
  --fp16 \
  --minShapes=input:1x3x224x224 \
  --optShapes=input:8x3x224x224 \
  --maxShapes=input:32x3x224x224 \
  --memPoolSize=workspace:4096

# 특정 형상으로 벤치마크(런타임 형상 지정)
trtexec --loadEngine=resnet50_dyn.plan --shapes=input:1x3x224x224   # 배치 1
trtexec --loadEngine=resnet50_dyn.plan --shapes=input:8x3x224x224   # 배치 8(opt)
trtexec --loadEngine=resnet50_dyn.plan --shapes=input:32x3x224x224  # 배치 32(max)

관찰(개략): 배치를 1→8→32로 늘리면 Throughput(qps·img/s)은 오르고(메모리 바운드 완화, 5.1.4) 요청당 지연은 늘어난다. opt(8)에서 효율이 좋고, opt에서 먼 형상(특히 1)은 상대적으로 비효율일 수 있다 — 이것이 "opt를 실제 흔한 배치에 맞추라"는 이유.

5.3.4.2 Python API — 동적 빌드(프로파일) + 가변 실행

빌드는 5.2.5의 build_engine에 프로파일이 이미 들어 있다(거기서 set_shape 했다). 추론에서 매 실행마다 형상을 지정하는 것이 동적 엔진의 핵심:

# infer_dyn.py — 같은 엔진을 여러 배치로 실행
import numpy as np, tensorrt as trt
from cuda import cudart   # 패키지·API 버전 확인

TRT_LOGGER = trt.Logger(trt.Logger.WARNING)

def load_engine(p):
    rt = trt.Runtime(TRT_LOGGER)
    with open(p, "rb") as f:
        return rt.deserialize_cuda_engine(f.read())

def run(engine, ctx, x):                    # x: (N,3,224,224) float32
    ctx.set_input_shape("input", x.shape)   # ★ 이번 실행의 실제 형상(min~max 안)
    assert ctx.all_binding_shapes_specified
    out_shape = tuple(ctx.get_tensor_shape("logits"))
    out = np.empty(out_shape, np.float32)

    _, d_in  = cudart.cudaMalloc(x.nbytes)
    _, d_out = cudart.cudaMalloc(out.nbytes)
    cudart.cudaMemcpy(d_in, x.ctypes.data, x.nbytes,
                      cudart.cudaMemcpyKind.cudaMemcpyHostToDevice)
    ctx.set_tensor_address("input",  int(d_in))
    ctx.set_tensor_address("logits", int(d_out))
    _, s = cudart.cudaStreamCreate()
    ctx.execute_async_v3(s); cudart.cudaStreamSynchronize(s)
    cudart.cudaMemcpy(out.ctypes.data, d_out, out.nbytes,
                      cudart.cudaMemcpyKind.cudaMemcpyDeviceToHost)
    cudart.cudaFree(d_in); cudart.cudaFree(d_out); cudart.cudaStreamDestroy(s)
    return out

if __name__ == "__main__":
    engine = load_engine("resnet50_dyn.plan")
    ctx = engine.create_execution_context()     # context는 재사용(매번 새로 만들지 말 것)
    for n in (1, 8, 32):                         # 하나의 엔진·하나의 context로 여러 배치
        y = run(engine, ctx, np.random.randn(n,3,224,224).astype(np.float32))
        print(f"batch {n:>2}: out={y.shape}")
    # 범위 밖이면 실패: run(engine, ctx, np.zeros((64,3,224,224),np.float32)) → 에러(>max)
python infer_dyn.py
# batch  1: out=(1, 1000)
# batch  8: out=(8, 1000)
# batch 32: out=(32, 1000)

핵심 습관: execution context와 GPU 버퍼는 재사용한다(매 요청 새로 만들면 오버헤드). 실무 서빙은 max 형상 기준 버퍼를 한 번 할당하고, 형상만 바꿔 실행 — 그 운영을 대신 해주는 것이 Triton(5.11).

5.3.5 워크스루 (2) — 프로파일을 실제 분포에 맞추기

opt 형상을 실제로 가장 흔한 형상에 맞추는 것이 동적 엔진 튜닝의 핵심이다. 워크스루:

1) 운영 로그에서 입력 형상 분포를 수집 예: 배치 분포 1·20%, 4·50%, 8·25%, 16· 5% → opt를 4(또는 4~8)로 (트래픽이 가장 몰리는 형상에 opt를 두어 평균 성능 최적) 2) min/max는 실제 하한·상한에 '정확히 맞게' max를 쓸데없이 크게(예: 256) 잡으면 활성화 메모리 낭비 → 동시 요청 수↓ min을 1로 두되, 1이 드물면 opt와 너무 멀어 1에서 비효율일 수 있음 3) 형상 분포가 '갈리면' 다중 프로파일 (5.3.6) 예: 저지연용 배치 1과 처리량용 배치 32가 둘 다 잦다 → 프로파일 2개 4) 측정으로 검증 대표 형상마다 trtexec --shapes로 지연· 처리량을 재고, opt 위치를 바꿔가며 '가중 평균 성능'(분포 가중)이 최선인 지점을 찾음

수치 규모(개략): 같은 동적 엔진이라도 opt=8로 빌드한 엔진은 배치 8 근처에서 가장 빠르고, opt=1로 빌드하면 배치 1에서 더 낫지만 큰 배치에서 손해를 볼 수 있다. "하나의 opt로 모든 형상 최적"은 불가 — 분포에 맞춘 opt + 필요 시 다중 프로파일이 답이다.

5.3.6 다중 optimization profile

형상 분포가 한 봉우리가 아니면(저지연 배치 1 + 고처리량 배치 32가 둘 다 잦음), 여러 프로파일을 한 엔진에 넣고 런타임에 고른다.

# 빌드: 프로파일 2개 — 하나는 작은 배치 최적, 하나는 큰 배치 최적
p_small = builder.create_optimization_profile()
p_small.set_shape("input", (1,3,224,224), (1,3,224,224), (4,3,224,224))
config.add_optimization_profile(p_small)        # profile index 0

p_large = builder.create_optimization_profile()
p_large.set_shape("input", (8,3,224,224), (16,3,224,224), (32,3,224,224))
config.add_optimization_profile(p_large)        # profile index 1
# build_serialized_network(...) → 두 프로파일을 가진 하나의 엔진
# 런타임: 형상에 맞는 프로파일을 선택(메서드명·방식은 버전 의존)
ctx.set_optimization_profile_async(0, stream)   # 작은 배치 요청엔 profile 0
ctx.set_input_shape("input", (1,3,224,224))
# ... 큰 배치 요청엔 profile 1로 전환(별도 context를 두는 패턴이 흔함)

트레이드오프:

  • 장: 각 형상대(帶)를 그 대에 최적화된 tactic으로 → 분포가 갈려도 전 구간 효율.
  • 단: 프로파일마다 메모리·엔진 크기 증가, 프로파일 전환 비용, 운영 복잡↑. 프로파일을 무한정 늘리지 말 것.
  • 실무 패턴: 프로파일당 별도 execution context를 두어 동시에 여러 형상대를 처리(컨텍스트는 프로파일에 묶임). 이 다중 컨텍스트·라우팅도 Triton(5.12 인스턴스·동시성)이 잘 다룬다.

5.3.7 shape tensor vs execution tensor (함정 주의)

대부분의 텐서는 execution tensor(데이터가 흐르는 보통 텐서)다. 그러나 일부 연산은 형상 값 자체를 입력으로 받는다(예: Reshape의 목표 형상, Resize의 크기) — 이를 shape tensor라 한다.

텐서 종류내용결정 위치
execution tensor픽셀·특징 같은 '데이터'GPU에서 계산됨
shape tensor형상·인덱스 같은 '메타 정수'종종 CPU/호스트에서 결정

shape tensor가 동적이면(런타임에 정해지면) 프로파일에서 그 값의 min/opt/max도 따로 지정해야 하고, 런타임에 set_shape_input(또는 버전별 대응 API)로 값을 넣어줘야 한다. 이를 빠뜨리면 빌드/실행 실패나 미정의 동작이 난다.

함정: "Reshape/Resize가 동적인데 왜 빌드가 안 되지?"의 흔한 원인이 shape tensor 프로파일 누락이다. ONNX export 시 형상이 상수로 굳어지면(정적) 문제없지만, 형상이 입력에 의존하면 shape tensor 처리가 필요하다. polygraphy inspect model로 어떤 텐서가 shape tensor인지 확인.

5.3.8 빌더 손잡이 — 깊게

5.2에서 목록만 본 손잡이를 동작과 함께.

손잡이동작언제
optimization level오토튜닝 탐색 강도(낮음~높음)개발=낮게(빠른 빌드), 배포=높게(빠른 엔진). 5.2.11 연습5
timing cache레이어별 tactic 측정 결과 캐시같은 GPU·같은 TRT면 재빌드 대폭 단축. 파일로 저장·재사용
tactic sources어떤 커널 라이브러리(cuBLAS·cuDNN·edge 등)를 탐색 후보로특정 소스 끄면 빌드 빨라지나 최적 tactic 놓칠 수도(버전 의존)
builder flagsFP16·INT8(5.4)·TF32·SPARSE_WEIGHTS(2:4, 1.14)·REFIT 등정밀도·희소성·refit 가능 엔진 등 빌드 특성 결정
REFIT가중치를 재빌드 없이 교체 가능한 엔진같은 구조에 가중치만 바뀔 때(파인튜닝 반복) 재빌드 절약. 약간의 성능 대가 가능
preview features신기능 미리보기 플래그(버전별)최신 최적화 실험. 변동 가능, 문서 확인

timing cache 실습 요령:

# 첫 빌드: 캐시 생성 / 이후 빌드: 캐시 재사용으로 오토튜닝 측정 생략
trtexec --onnx=resnet50.onnx --saveEngine=a.plan --fp16 \
        --timingCacheFile=trt.cache    # 없으면 만들고, 있으면 읽어 가속
# 같은 GPU·TRT에서 두 번째 빌드부터 빌드 시간이 눈에 띄게 줄어듦(개략)

정직: flag·preview feature 이름과 가용성은 버전마다 다르다. SPARSE_WEIGHTS(구조적 희소, 1.14)는 가중치가 2:4 패턴이고 하드웨어가 지원해야 실효가 있다 — 켠다고 자동으로 빨라지지 않는다. 항상 그 버전 문서 + 실측.

5.3.9 중간 점검 (체크포인트)

  • [ ] 정적 vs 동적 shape의 트레이드오프를 대고, 어떤 축(배치·시퀀스·해상도)을 왜 동적으로 두는지 설명한다.
  • [ ] optimization profile의 min/opt/max 역할을 각각 말하고, opt가 성능에, max가 메모리에 미치는 영향을 안다.
  • [ ] 하나의 동적 엔진을 여러 형상으로 실행하고(Python/trtexec), 범위 밖 입력이 거부됨을 확인한다.
  • [ ] 입력 분포에 맞춰 opt를 정하고, 분포가 갈리면 다중 프로파일을 쓸 줄 안다.
  • [ ] shape tensor와 execution tensor를 구분하고, 동적 형상 입력의 프로파일 누락 함정을 안다.
  • [ ] timing cache·optimization level로 빌드 시간을, builder flag로 엔진 특성을 조절한다.

증상→의심: 배치 1은 빠른데 큰 배치가 비효율 → opt가 작은 배치에 맞춰짐 → opt 조정·다중 프로파일. 활성화 메모리가 과도, 동시 요청 수↓ → max를 너무 크게 잡음 → 실제 상한으로 축소. 런타임에 "shape out of range" 에러 → 입력이 min~max 밖 → 범위 확대 또는 입력 클램프. Reshape/Resize 동적 모델 빌드 실패 → shape tensor 프로파일 누락. 재빌드가 매번 느림 → timing cache 미사용.

5.3.10 트레이드오프·대안

동적 범위를 얼마나 넓게? 핵심 결정.

선택누구에게비고
좁은 동적 범위 + opt 정조준형상 분포가 한 봉우리단순·고성능. 범위 밖 입력은 거부(클램프·패딩 필요)
넓은 동적 범위(1개 프로파일)형상이 매우 다양·예측 어려움운영 단순하나 형상별 최적화 무뎌짐·메모리(max)↑
다중 프로파일분포가 둘 이상 봉우리(저지연+고처리량)전 구간 효율, 운영·메모리 복잡↑
형상대별 정적 엔진 여러 개형상 종류가 소수로 고정각 형상 최대 성능, 엔진·적재 관리 부담

견해: "실제 분포에 범위를 정확히 맞추고 opt를 트래픽 봉우리에 두는" 게 대개 최선이다. 막연히 1~256 같은 넓은 범위를 잡으면 메모리·성능을 둘 다 손해 본다. 형상이 정말 소수(예: 224²와 640² 둘뿐)면 정적 엔진 2개가 더 빠를 수도 있다 — 측정으로 결정. 가변 시퀀스 LLM은 이 일반 TRT 동적 shape가 아니라 TRT-LLM의 인플라이트 배칭·페이지드 KV(5.7~5.8)가 더 적합하다.

비엔비디아 정직: ONNX Runtime 등도 동적 shape를 지원하지만, 형상별 오토튜닝의 깊이·최신 정밀도 흡수가 TRT의 차별점이다. 반대로 형상이 극도로 가변적이고 컴파일 비용이 부담이면, 컴파일 없는 런타임이 운영상 단순할 수 있다 — 워크로드로 저울질.

5.3.11 흔한 함정

  • opt를 임의로(예: max와 동일). opt는 가장 흔한 형상에 둬야 평균 성능이 산다. max=opt로 두면 작은 배치가 비효율.
  • max를 과도하게 크게. 활성화 메모리를 max 기준으로 잡으므로 동시 요청 수·다른 모델 적재를 제한한다. 실제 상한에 맞춰라.
  • 범위 밖 입력 미처리. min보다 작거나 max보다 큰 입력은 런타임 거부. 서빙단에서 패딩·클램프·라우팅 정책 필요.
  • shape tensor 프로파일 누락. 동적 Reshape/Resize 등은 형상 값의 min/opt/max도 지정·주입해야 한다. 빠뜨리면 빌드/실행 실패.
  • context를 매 요청 새로 만든다. execution context·버퍼는 재사용. 매번 생성은 큰 오버헤드(서빙은 Triton에 위임).
  • 다중 프로파일을 남발. 프로파일마다 메모리·복잡도 증가. 분포가 실제로 갈릴 때만, 최소 개수로.
  • timing cache를 GPU/버전 넘나들며 재사용. 캐시는 같은 GPU·같은 TRT에서만 유효 — 다르면 무효(또는 무시)되니 환경별로 관리.
  • 빌더 flag를 켜면 자동 가속이라 착각. SPARSE_WEIGHTS·FP16 등은 하드웨어·가중치 조건이 맞아야 실효. 켜고 끝이 아니라 실측으로 확인.

5.3.12 연습문제

연습 1 (min/opt/max). 배치만 동적인 엔진을 min=1, opt=8, max=32로 빌드했다. (a) 런타임에 배치 4를 넣으면? (b) 배치 48을 넣으면? (c) 배치 1과 8 중 어디가 더 효율적일 가능성이 큰가, 왜? (d) max를 128로 높이면 무엇이 나빠지나?

정답

(a) 정상 실행(1~32 안). 단 opt(8)에서 멀수록 비최적일 수 있어 4는 8보다 약간 덜 최적일 수 있음. (b) 실행 거부(>max=32) — "shape out of range". (c) 배치 8 — 빌더가 opt=8에 맞춰 tactic을 골랐기 때문. 배치 1은 opt에서 멀고 메모리 바운드라 GPU도 덜 채워짐(5.1). (d) max=128이면 활성화 메모리를 128 기준으로 할당 → 메모리 낭비, 동시 요청 수·다른 모델 적재 여력↓. 실제로 48만 들어온다면 max=48이 적절.

연습 2 (분포 맞추기). 입력 배치 분포가 [1:10%, 2:40%, 4:35%, 8:15%]이다. (a) opt를 어디에 둘지와 이유, (b) min/max는? (c) 측정으로 검증하는 절차.

정답

(a) opt를 2 또는 2~4 사이(트래픽 봉우리)에 — 가장 잦은 형상에서 최적이라야 가중 평균 성능이 산다. (단순히 평균 배치가 아니라 빈도 가중으로 본다.) (b) min=1, max=8(실제 하한·상한에 딱). 더 넓게 잡을 이유 없음. (c) 각 배치(1·2·4·8)를 trtexec --shapes로 지연·처리량 측정 → 빈도로 가중한 평균 성능을 계산 → opt를 2·3·4로 바꿔 빌드해 비교 → 가중 평균이 최선인 opt 채택. 봉우리가 둘로 갈리면 다중 프로파일 고려.

연습 3 (다중 프로파일). 한 서비스가 (i) 대화형 저지연(배치 1, p99 중요)과 (ii) 야간 배치 처리(배치 32, 처리량 중요)를 같은 모델로 한다. 단일 프로파일과 다중 프로파일 중 무엇이 맞고, 어떻게 구성·운영하나?

정답

다중 프로파일이 맞다 — 두 봉우리(1과 32)가 성격까지 달라 하나의 opt로는 둘 다 만족 못 함. 구성: 프로파일0=min1/opt1/max4(저지연용), 프로파일1=min8/opt32/max32(처리량용)을 한 엔진에. 운영: 프로파일당 별도 execution context를 두고, 대화형 요청은 context0(profile0)으로 작은 배치, 야간 배치는 context1(profile1)로 큰 배치 실행. 라우팅·동시성 관리는 Triton(5.12)으로. 메모리는 두 프로파일 합이라 여유 확인.

연습 4 (shape tensor). 동적 해상도를 받는 세그멘테이션 모델을 빌드하니 Resize 레이어에서 실패한다. 원인 후보와 진단·해결을 설명하라.

정답

원인 후보: Resize의 목표 크기가 shape tensor(형상 값 입력) 인데 동적이고, 그 값의 프로파일(min/opt/max)·런타임 주입을 안 했다. 진단: polygraphy inspect model로 어떤 입력이 shape tensor인지 확인. 해결: ① 그 shape tensor에 대해 프로파일에서 값의 min/opt/max를 지정하고 런타임에 set_shape_input(버전별 API)으로 값 주입, 또는 ② 가능하면 export 단계에서 형상을 execution tensor 기반(스케일·상수) 으로 바꿔 shape tensor 의존을 없앰. 해상도 종류가 소수면 정적 엔진 여러 개도 대안.

연습 5 (빌드 손잡이). 같은 구조 모델을 가중치만 바꿔 자주 재배포한다(파인튜닝 반복). 매번 풀 빌드(오토튜닝)가 부담이다. 두 가지 이상 완화책과 트레이드오프.

정답

① REFIT 가능 엔진으로 빌드해 두고, 가중치만 재빌드 없이 교체(refit) → 오토튜닝 생략. 트레이드오프: refit 엔진은 약간의 성능 손실 가능, 구조가 같아야 함. ② timing cache 재사용(같은 GPU·TRT) → 풀 빌드해도 오토튜닝 측정은 캐시에서 → 빌드 시간 단축. ③ 개발 중 optimization level↓로 빠르게 반복, 최종 배포만 레벨↑. 조합: REFIT로 일상 교체 + 주기적으로 풀 빌드(level↑·캐시)로 성능 리프레시. 핵심은 "구조 불변·가중치 변화"를 이용해 오토튜닝 반복을 피하는 것.

5.3.13 요약 · 다음 장

  • 동적 shape는 가변 부하(배치·시퀀스·해상도)를 하나의 엔진으로 서빙한다. 대가는 "범위가 넓을수록 형상별 최적화가 무뎌짐" — 실제 분포에 범위를 맞추는 것이 핵심.
  • optimization profile(min/opt/max): 빌더는 opt 형상에 맞춰 tactic을 고르고, max 기준으로 메모리를 할당한다. opt는 트래픽 봉우리에, max는 실제 상한에.
  • 분포가 갈리면 다중 프로파일(프로파일당 별도 context). shape tensor(형상 값 입력)는 값의 프로파일·주입이 따로 필요 — 동적 Reshape/Resize 실패의 단골 원인.
  • 빌더 손잡이: optimization level·timing cache·tactic source·builder flag(FP16·INT8·SPARSE·REFIT). flag는 하드웨어·조건이 맞아야 실효 — 켜고 끝이 아니라 실측.
  • 정직: API·flag는 버전 의존(확인 필요), 모든 성능·메모리 수치는 실측, 가변 시퀀스 LLM은 일반 TRT가 아니라 TRT-LLM(5.7~).

다음 장: 5.4 TensorRT 정밀도 I — INT8 PTQ·캘리브레이션. 사면체의 정밀도 축을 본격적으로 판다. FP16을 넘어 INT8 후처리 양자화(PTQ) 로 처리량·메모리를 더 밀되, 캘리브레이션으로 정확도를 지키는 법 — 스케일 산정·캘리브레이션 데이터·태스크 지표 검증을 실습으로 따라간다.