AEGLOSLAB 엔비디아 기술 스택

제5부 추론 최적화와 서빙

5.4 TensorRT 정밀도 I — INT8 PTQ·캘리브레이션

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

5.2~5.3에서 TensorRT 엔진을 빌드하고 동적 shape를 다뤘다. 그때 정밀도는 --fp16 정도만 켰다. 이 장은 사면체(5.1)의 정밀도 축을 본격적으로 민다 — INT8 후처리 양자화(PTQ, Post-Training Quantization). 학습을 다시 하지 않고, 캘리브레이션 데이터로 스케일만 정해 가중치·활성화를 8비트 정수로 바꿔 메모리·대역폭을 줄이고 INT8 텐서코어로 처리량을 높인다. 핵심 난제는 "어떻게 정확도를 지키느냐" — 그것이 캘리브레이션이다.

배우는 것:

  • 왜 INT8인가 — FP32 대비 4×, FP16 대비 2× 작은 메모리·대역폭, INT8 텐서코어 처리량(1.11~1.12)과 사면체 이득.
  • 양자화의 수학 — 실수 범위를 정수 [-127, 127]로 매핑하는 스케일, 대칭/비대칭, per-tensor vs per-channel.
  • PTQ vs QAT — 재학습 없이(PTQ) 스케일만 정하기 vs 학습으로 양자화를 인지시키기(QAT, 5.5).
  • 캘리브레이션 — 대표 데이터로 활성화 분포를 모아 정보 손실을 최소화하는 스케일을 고르는 법(minmax·percentile·KL/엔트로피).
  • TensorRT calibrator 구현·캘리브레이션 캐시, trtexec --int8 실습.
  • 정확도 검증과 민감 레이어 혼합정밀 — 태스크 지표로 판정하고, 안 되면 일부 레이어를 고정밀로 되돌리기.

선수 지식: 1.13~1.14(부동소수·정밀도·양자화 기초 — 이 장의 토대), 5.2~5.3(빌드 흐름·builder flag·정확도 검증 polygraphy), 5.1(정밀도 축·"양자화는 검증과 함께"), 1.11~1.12(INT8 텐서코어). 환경은 5.2과 동일.

정직: TensorRT의 calibrator 클래스·INT8 API는 버전마다 다르다(implicit 캘리브레이션 → explicit Q/DQ로 무게중심 이동, 클래스 개명·deprecation). 이 장은 개념과 흐름을 중심에 두고, 코드는 최근 계열의 한 방식을 예시한다 — 정확한 클래스·시그니처는 당신 버전 문서로 확인. 정확도·속도 수치는 모두 환경 의존 개략값, 직접 실측.

5.4.2 왜 INT8인가 — 사면체 이득과 대가

FP32 (4B) 기준 FP16/BF16 (2B) 메모리 1/2 · 텐서코어 INT8·FP8 (1B) 메모리 1/4 · INT8·FP8 텐서코어 FP4 (0.5B, 4.4) 메모리 1/8 · FP8=Hopper·FP4=Blackwell(4.2·4.4)

INT8이 사면체를 미는 방식:

  • 메모리·대역폭 ↓: 가중치·활성화가 1바이트 → FP16의 절반, FP32의 1/4. decode·메모리 바운드 구간(5.1.4)에서 읽을 바이트가 줄어 빨라지고, KV·활성화 메모리가 줄어 동시 요청 수↑.
  • 처리량 ↑: INT8 텐서코어(1.11~1.12)는 같은 면적에서 더 많은 정수 MAC을 처리 → 컴퓨트 바운드 레이어도 빨라짐.
  • 대가 = 정밀도: 8비트는 표현 가능한 값이 256개뿐. 범위를 잘못 잡으면 클리핑(범위 밖 잘림) 또는 양자화 잡음(해상도 부족) 으로 정확도가 무너진다. 이 대가를 관리하는 것이 캘리브레이션.

핵심: INT8은 "무비용 속도"가 아니다. 반드시 태스크 지표로 정확도를 검증하고(5.1.11, 5.2.6), 통과할 때만 켠다. 분류처럼 견고한 태스크는 정확도를 유지하고, 회귀·생성·민감한 검출은 더 조심해야 한다.

5.4.3 양자화의 수학 — 스케일·대칭·per-channel

대칭 선형 양자화(TensorRT가 주로 쓰는 방식)의 핵심 한 줄:

   q = round( clip(x, -t, +t) / scale ),     scale = t / 127
   역양자화:  x' = q * scale
   여기서 t = '동적 범위(dynamic range)' 임계값 = 그 텐서에서 표현할 최대 절댓값
  • scale(스케일): 실수 1 LSB가 정수로 얼마인지. t(범위)를 127등분. t를 어떻게 고르냐가 캘리브레이션의 전부.
  • 대칭(symmetric): 0을 중심으로 [-t, +t]. 영점(zero-point)=0이라 곱셈이 단순·빠르다. (비대칭은 영점≠0으로 한쪽으로 치우친 분포에 유리하나 TRT는 대칭 중심.)
  • 클리핑 vs 해상도 트레이드오프:
    • t를 크게(예: 최대 절댓값) → 클리핑 없음, 그러나 흔한 값들이 적은 정수 칸을 써 해상도 손실(양자화 잡음↑).
    • t를 작게(이상치 무시) → 흔한 값에 해상도 집중, 그러나 큰 값이 클리핑됨.
    • 최적 t는 둘 사이 — 정보 손실을 최소화하는 지점(KL 캘리브레이션의 동기).
|x| 값 빈도 t_kl t_big 이상치(outlier)
활성화 히스토그램(예) — t를 어디에 그을까?

t를 어디에 긋느냐가 관건이다: t_big(이상치까지 포함) → 해상도 낭비, t_kl(정보손실 최소, KL) → 보통 최선, t_small(이상치 클리핑) → 큰 값 손실.

per-tensor vs per-channel (매우 중요):

방식스케일 개수어디에
per-tensor텐서당 1개주로 활성화(런타임 가변, 캘리브레이션으로 추정)
per-channel출력 채널당 1개주로 가중치(채널별 분포 차이가 커서 채널마다 스케일이라야 정확)

가중치를 per-tensor로 양자화하면 채널 간 스케일 차이를 한 값으로 합쳐 정확도가 크게 떨어진다 — 가중치는 per-channel이 정석(TRT가 자동으로 적용). 활성화는 채널별로 분리하기 어려워 보통 per-tensor + 캘리브레이션.

5.4.4 PTQ vs QAT — 두 갈래

INT8로 가는 두 길:

PTQ(후처리 양자화, 이 장) QAT(학습 인지 양자화, 5.5) 학습된 모델 그대로 + 캘리브레이션 데이터 활성화 스케일 추정 재학습 없음(빠름·간단) 학습 그래프에 가짜양자화 (fake-quant) 삽입 미세조정 학습 모델이 양자화에 적응 재학습 필요(느림·정확도 최선)

대부분 PTQ로 시작, 정확도 부족하면 QAT로.

  • PTQ: 가장 적은 노력. 학습 파이프라인·라벨 불필요(캘리브레이션은 라벨 없는 대표 입력이면 충분). 정확도 손실이 허용 범위면 끝.
  • QAT(5.5): PTQ로 정확도가 모자랄 때. 학습 중 양자화를 시뮬레이션해 모델이 적응 → 보통 PTQ보다 정확하나 재학습 비용.

견해: PTQ를 먼저 시도하라 — 비용이 낮고 빠르다. 분류·다수 CNN은 PTQ로 충분할 때가 많다. 태스크 지표가 임계 이상 떨어지면(예: top-1 −1%p 초과), 민감 레이어 혼합정밀(5.4.8)을 먼저 시도하고, 그래도 안 되면 QAT(5.5).

5.4.5 캘리브레이션의 작동 — TensorRT 내부

활성화의 동적 범위 t는 데이터에 의존한다(런타임에 흐르는 값의 분포). TensorRT는 캘리브레이션 데이터셋을 모델에 흘려 각 활성화 텐서의 분포를 수집하고 t를 정한다.

선택지 선택지 선택지 1) 대표 입력 배치들을 모델에 forward (FP32로) 2) 각 양자화 대상 활성화 텐서의 값 히스토그램 누적 3) 알고리즘으로 t 선택 MinMax: t = 관측된 최대 절댓값 (단순, 이상치에 취약) Percentile: t = 99.9%· 99.99% 분위수 (이상치 무시) Entropy/KL: 양자화 전후 분포의 KL divergence 최소화하는 t (TRT 기본 계열) 4) t로 scale 계산 → 엔진에 각 텐서 스케일 고정 5) 결과를 calibration cache에 저장(재빌드 시 재사용)

KL(엔트로피) 캘리브레이션(TensorRT의 대표 방식)의 요지: 원래 FP 분포 P와, t로 잘라 INT8로 양자화한 분포 Q를 비교해, 두 분포의 정보 차이(KL divergence)가 가장 작은 t 를 고른다. "이상치를 조금 클리핑하더라도 전체 분포 모양을 가장 잘 보존하는" 지점이다 — minmax(이상치에 끌려감)보다 대개 정확.

캘리브레이터(개념)특징적합
Entropy(KL) 계열정보손실 최소, 분포 보존일반적 기본(대부분 CNN)
MinMax 계열관측 최대로 범위, 클리핑 없음범위가 중요한 일부 연산·이상치 적을 때
Percentile분위수로 이상치 제외이상치(아웃라이어)가 두드러질 때

캘리브레이션 데이터의 조건(가장 중요):

  • 대표성: 실제 추론에서 들어올 입력 분포를 닮아야 한다. 학습셋 일부·검증셋 일부가 흔히 쓰인다. 편향된 데이터(예: 한 클래스만) 로 캘리브레이션하면 스케일이 틀어진다.
  • 양: 보통 수백~수천 샘플(태스크·모델 의존). 너무 적으면 통계 부정확, 너무 많으면 캘리브레이션만 느려짐.
  • 라벨 불필요: 캘리브레이션은 활성화 분포만 보므로 라벨이 없어도 된다(검증은 별개로 라벨 필요).

5.4.6 워크스루 (1) — trtexec로 INT8 빠르게

가장 빠른 출발. 단, 캘리브레이션 없이 --int8만 주면 TRT가 임의/단순 범위를 쓸 수 있어 정확도 보장이 안 된다 — 빠른 성능 가늠용으로만.

# 성능 가늠용(정확도 비보장): INT8 텐서코어가 주는 속도 상한을 본다
trtexec --onnx=resnet50.onnx --saveEngine=r50_int8_perf.plan \
        --int8 --optShapes=input:8x3x224x224
# FP16 엔진(5.2)과 Throughput·Latency·엔진 크기 비교 → INT8 잠재 이득 확인

정확도까지 보려면 캘리브레이션 캐시를 함께 준다. 캐시는 Python calibrator(5.4.7)로 한 번 만들어 두고 재사용:

# 캘리브레이션 캐시를 사용해 빌드(정확도 의미 있는 INT8 엔진)
trtexec --onnx=resnet50.onnx --saveEngine=r50_int8.plan \
        --int8 --calib=resnet50_calib.cache \
        --optShapes=input:8x3x224x224

체크: --int8만으로 빠르다고 그대로 배포하면 안 된다. 반드시 캘리브레이션 + 태스크 지표 검증(5.4.9). trtexec의 INT8 단독은 "얼마나 빨라질 수 있나"의 상한 탐색일 뿐.

5.4.7 워크스루 (2) — Python calibrator 구현

PTQ의 본체. 대표 데이터를 배치로 공급하는 calibrator를 구현해 빌드에 연결한다. (클래스명·메서드는 버전 의존 — 흐름에 집중.)

# calibrator.py — Entropy 캘리브레이터(개념 예시; 버전별 API 확인)
import os, numpy as np, tensorrt as trt
from cuda import cudart

class ImageCalibrator(trt.IInt8EntropyCalibrator2):
    """대표 이미지 배치를 TRT에 공급해 활성화 스케일을 추정.
       cache가 있으면 데이터 없이 재사용(재빌드 가속)."""
    def __init__(self, data, batch=8, cache="resnet50_calib.cache"):
        super().__init__()
        self.data = data                  # np.float32 (M,3,224,224) — 전처리까지 끝낸 대표 입력
        self.batch = batch
        self.cache_path = cache
        self.idx = 0
        nbytes = batch*3*224*224*4
        _, self.d_in = cudart.cudaMalloc(nbytes)

    def get_batch_size(self): return self.batch

    def get_batch(self, names):           # 다음 배치를 GPU로 올려 포인터 반환. 끝나면 None
        if self.idx + self.batch > len(self.data):
            return None
        chunk = np.ascontiguousarray(self.data[self.idx:self.idx+self.batch])
        cudart.cudaMemcpy(self.d_in, chunk.ctypes.data, chunk.nbytes,
                          cudart.cudaMemcpyKind.cudaMemcpyHostToDevice)
        self.idx += self.batch
        return [int(self.d_in)]

    def read_calibration_cache(self):     # 캐시 있으면 캘리브레이션 생략
        if os.path.exists(self.cache_path):
            with open(self.cache_path, "rb") as f: return f.read()
        return None

    def write_calibration_cache(self, cache):
        with open(self.cache_path, "wb") as f: f.write(cache)
# build_int8.py — calibrator를 붙여 INT8 엔진 빌드
import tensorrt as trt, numpy as np
from calibrator import ImageCalibrator

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

def build_int8(onnx_path, plan_path, calib_data):
    builder = trt.Builder(TRT_LOGGER)
    network = builder.create_network(1 << int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH))
    parser  = trt.OnnxParser(network, TRT_LOGGER)
    with open(onnx_path, "rb") as f:
        assert parser.parse(f.read()), "ONNX parse failed"

    config = builder.create_builder_config()
    config.set_memory_pool_limit(trt.MemoryPoolType.WORKSPACE, 4 << 30)
    config.set_flag(trt.BuilderFlag.INT8)                 # ★ INT8 활성화
    # (권장) FP16도 함께 켜 INT8 부적합 레이어가 FP16로 떨어지게 → 정확도·속도 균형
    config.set_flag(trt.BuilderFlag.FP16)
    config.int8_calibrator = ImageCalibrator(calib_data)  # ★ 캘리브레이터 연결

    prof = builder.create_optimization_profile()
    prof.set_shape("input", (1,3,224,224), (8,3,224,224), (32,3,224,224))
    config.add_optimization_profile(prof)
    # INT8 캘리브레이션은 보통 단일 형상(calibration profile) 기준 — 버전별 규칙 확인

    serialized = builder.build_serialized_network(network, config)
    assert serialized is not None, "INT8 build failed"
    with open(plan_path, "wb") as f: f.write(serialized)
    print("wrote", plan_path)

if __name__ == "__main__":
    # 대표 입력 ~512장(전처리 완료). 실제론 검증셋 일부를 모델 입력 형태로.
    calib = np.random.randn(512,3,224,224).astype(np.float32)   # ← 실데이터로 교체
    build_int8("resnet50.onnx", "resnet50_int8.plan", calib)
python build_int8.py     # 첫 실행: 캘리브레이션 수행 + resnet50_calib.cache 생성
                          # 재실행: 캐시 재사용으로 캘리브레이션 생략(빌드 가속)

정직: np.random은 대표 데이터가 아니다 — 데모용 자리표시일 뿐. 실제로는 검증셋/운영 로그의 실제 입력을 전처리해 넣어야 스케일이 의미 있다. 무작위 데이터로 캘리브레이션하면 정확도가 망가진다.

5.4.8 워크스루 (3) — 정확도 검증·민감 레이어 혼합정밀

INT8 엔진은 반드시 태스크 지표로 검증한다(5.1.11, 5.2.6).

# 1) 대표 평가셋으로 FP32(또는 FP16) 대비 정확도 비교 — '수치 오차'가 아니라 '태스크 지표'
#    분류: top-1/top-5,  검출: mAP,  세그: mIoU,  생성: 해당 지표
python eval_accuracy.py --engine resnet50_int8.plan   --metric top1
python eval_accuracy.py --engine resnet50_fp16.plan   --metric top1
#    예: FP16 top1=76.1%, INT8 top1=75.7% → -0.4%p. 허용선(예: -1%p) 안이면 통과(개략)

정확도가 임계 밖이면 민감 레이어를 고정밀로 되돌리는 혼합정밀:

1) 레이어별 민감도 분석: 한 레이어씩 INT8↔FP16 바꿔 정확도 영향 측정 (polygraphy/TRT 도구로 레이어별 비교, 또는 한 레이어씩 제외) 2) 가장 민감한 소수 레이어(첫·마지막 레이어, 특정 정규화·어텐션 등)를 FP16/FP32 강제 3) 나머지는 INT8 유지 → 정확도 회복 + 속도 대부분 유지
# 특정 레이어의 정밀도를 강제(개념; API는 버전 확인)
for i in range(network.num_layers):
    layer = network.get_layer(i)
    if layer.name in SENSITIVE_LAYERS:        # 민감도 분석으로 찾은 레이어들
        layer.precision = trt.float16          # 이 레이어는 INT8 대신 FP16
        layer.set_output_type(0, trt.float16)
config.set_flag(trt.BuilderFlag.OBEY_PRECISION_CONSTRAINTS)  # 강제 준수(버전별 플래그)

흔한 민감 지점: 모델의 첫 레이어와 마지막(분류) 레이어, 값 범위가 넓은 정규화·소프트맥스 직전, 어텐션 일부. 전부 INT8로 밀지 말고 소수 민감 레이어만 고정밀로 두면 "거의 INT8 속도 + 정확도 회복"을 얻을 때가 많다.

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

  • [ ] INT8이 사면체의 어느 축(메모리·처리량)을 어떻게 밀고, 대가(정밀도)가 무엇인지 설명한다.
  • [ ] 대칭 양자화의 scale = t/127, 클리핑 vs 해상도 트레이드오프, 가중치=per-channel·활성화=per-tensor의 이유를 안다.
  • [ ] PTQ vs QAT를 구분하고 PTQ를 먼저 쓰는 이유를 제시한다.
  • [ ] 캘리브레이션이 대표 데이터로 활성화 분포를 모아 t를 정하는 것임을, KL/minmax/percentile의 차이와 함께 설명한다.
  • [ ] Python calibrator를 구현·연결해 INT8 엔진을 빌드하고, 캘리브레이션 캐시를 재사용한다.
  • [ ] 태스크 지표로 정확도를 검증하고, 임계 밖이면 민감 레이어 혼합정밀로 회복한다.

증상→의심: INT8 정확도가 크게 떨어짐 → 캘리브레이션 데이터 비대표/부족, 또는 가중치 per-tensor·민감 레이어까지 INT8 → 데이터 교체·per-channel 확인·민감 레이어 FP16. INT8인데 별로 안 빠름 → INT8 텐서코어 미사용(형상·세대), 또는 많은 레이어가 FP16로 폴백 → 프로파일·레이어 정밀도 확인. 재빌드마다 캘리브레이션 반복(느림) → 캐시 미사용. trtexec --int8 단독이 부정확 → 캘리브레이션 캐시 없이 빌드한 것(정상; 캐시 제공 필요).

5.4.10 트레이드오프·대안

어느 정밀도로? 사면체 위 선택.

정밀도누구에게비고
FP16/BF16정확도 민감·노력 최소안전한 기본. 텐서코어 가속, 정확도 손실 거의 없음
INT8 PTQ처리량·메모리 압박, 견고한 태스크노력 적음, 정확도 검증 필수. 대다수 CNN OK
INT8 QAT(5.5)PTQ로 정확도 부족할 때재학습 비용, 정확도 최선
FP8(4.2·5.5)Hopper+ 세대, LLM·트랜스포머세대 의존. INT8보다 트랜스포머에 유리한 경우
혼합(INT8+FP16)INT8이 일부 레이어에서만 망가질 때민감 레이어만 고정밀 — 실무 단골

견해: FP16으로 안전하게 시작 → 처리량/비용이 아프면 INT8 PTQ 시도 → 정확도 부족분은 혼합정밀 → 그래도 안 되면 QAT. 이 사다리를 한 칸씩 올라가는 것이 노력 대비 효율적이다. 트랜스포머·LLM은 INT8보다 FP8(5.5·4.2)이 더 자연스러운 경우가 많다 — 텐서코어·트랜스포머 엔진 지원과 함께 판단.

비엔비디아 정직: 양자화는 TRT만의 것이 아니다 — ONNX Runtime·다른 런타임도 INT8 PTQ/QAT를 지원하고, 양자화 표현(Q/DQ ONNX 노드)은 표준에 가깝다. TRT의 강점은 INT8 텐서코어·per-channel·엔트로피 캘리브레이션·혼합정밀의 통합과 그 GPU에서의 오토튜닝이다. 정확도·속도는 반드시 당신 모델·평가셋으로 실측.

5.4.11 흔한 함정

  • 정확도를 수치 오차로 판정. INT8은 원소별 오차가 크다(정상). --atol 비교로 FAIL을 보고 포기하지 말 것 — 태스크 지표(top-1·mAP 등)로 판정(5.2.11 연습3).
  • 비대표 캘리브레이션 데이터. 무작위·편향(한 클래스)·전처리 불일치 데이터로 캘리브레이션하면 스케일이 틀어져 정확도 붕괴. 실제 입력 분포 + 동일 전처리.
  • 캘리브레이션 데이터 전처리 불일치. 추론 시 정규화·리사이즈와 다른 전처리로 캘리브레이션하면 활성화 분포가 달라 스케일이 어긋난다. 추론과 똑같이.
  • 가중치를 per-tensor로. 가중치는 채널별 분포 차가 커서 per-channel이라야 한다(TRT 기본). 임의로 끄지 말 것.
  • 모든 레이어를 INT8로 강행. 첫·마지막·민감 레이어까지 INT8로 밀면 정확도가 무너진다. 민감 레이어는 FP16/FP32 혼합.
  • 캐시를 GPU/모델/전처리 바뀌어도 재사용. 캘리브레이션 캐시는 그 모델·그 데이터 분포 기준. 모델/전처리가 바뀌면 재캘리브레이션.
  • --int8 단독을 정확도 보장으로 오해. 캘리브레이션 없는 INT8은 성능 가늠용. 배포엔 캘리브레이션+검증.
  • 검증을 캘리브레이션 데이터로. 캘리브레이션에 쓴 데이터로 정확도를 재면 낙관 편향. 분리된 평가셋으로.

5.4.12 연습문제

연습 1 (양자화 수학). 어떤 활성화 텐서의 값이 대부분 [-2, 2]인데 드물게 8까지 벗어난다. (a) MinMax로 t를 잡으면 흔한 값들의 해상도는? (b) KL/Percentile로 $t\approx2.5$를 잡으면 8은 어떻게 되나? (c) 어느 쪽이 보통 정확도에 유리한가, 왜?

정답

(a) MinMax는 t=8 → scale=8/127≈0.063. 대부분의 값 [-2,2]는 정수 약 ±32칸만 써 해상도가 낮다(이상치 8 때문에 흔한 값이 손해). (b) $t\approx2.5$ → 8은 +127로 클리핑(소수 이상치 손실). 흔한 값 [-2,2]는 약 ±100칸을 써 해상도가 높다. (c) 보통 KL/Percentile이 유리 — 소수 이상치를 약간 클리핑하더라도 흔한 값의 해상도를 살려 전체 정보 손실(분포 보존)이 작다. 이것이 TRT가 엔트로피 캘리브레이션을 기본 계열로 두는 이유. (단, 이상치가 의미 있는 태스크면 percentile 분위수·범위 조정 필요.)

연습 2 (per-channel). 합성곱 가중치를 per-tensor로 양자화했더니 정확도가 크게 떨어졌다. 원인과 해결, 그리고 활성화는 보통 per-tensor인 이유.

정답

원인: 출력 채널마다 가중치 분포(범위)가 크게 달라, 한 스케일로 모든 채널을 하나로 합치면 작은 범위 채널은 해상도 손실, 큰 범위 채널은 클리핑 → 정확도 붕괴. 해결: per-channel 양자화(채널별 스케일) — TRT 기본. 활성화가 per-tensor인 이유: 활성화는 런타임에 가변이고 채널별로 분리해 캘리브레이션하기 어렵고 비용이 큼. 가중치는 빌드 시점에 값이 고정돼 채널별 스케일을 정확히 계산할 수 있어 per-channel이 자연스럽다.

연습 3 (캘리브레이션 데이터). 동료가 학습셋에서 "고양이 클래스 이미지 1000장"만으로 캘리브레이션해 다중 클래스 분류 INT8 엔진을 만들었더니 다른 클래스 정확도가 나쁘다. 무엇이 문제이고 어떻게 고치나? 데이터 양·라벨에 대한 조언도.

정답

문제: 비대표 캘리브레이션 데이터(한 클래스만) → 활성화 분포가 실제 추론 분포(전 클래스)와 달라 스케일이 한쪽으로 편향 → 다른 클래스에서 클리핑/해상도 손실. 고치기: 전 클래스를 고루 포함한 대표 입력(검증셋 무작위 샘플 등)으로 재캘리브레이션, 추론과 동일한 전처리 적용. 양: 보통 수백~수천 샘플로 충분(태스크 의존, 너무 적으면 통계 부정확). 라벨: 캘리브레이션은 활성화 분포만 보므로 라벨 불필요 — 단, 최종 정확도 검증은 라벨 있는 별도 평가셋으로(캘리브레이션 데이터와 분리).

연습 4 (혼합정밀). INT8 PTQ로 검출 모델 mAP가 기준 대비 −3%p 떨어졌다(허용 −1%p). QAT로 바로 가기 전에 시도할 단계와, 어떤 레이어를 의심할지.

정답

QAT 전에 민감 레이어 혼합정밀을 시도: ① 레이어별 민감도 분석(한 레이어씩 INT8↔FP16 전환해 mAP 영향 측정) → ② 가장 민감한 소수 레이어를 FP16/FP32로 강제, 나머지 INT8 유지. 의심 레이어: 첫 입력 레이어(원시 입력 분포가 넓음), 마지막 검출 헤드/회귀 출력(좌표·신뢰도는 해상도 민감), 값 범위가 넓은 정규화·소프트맥스 직전, 특정 어텐션. 이렇게 소수 레이어만 고정밀로 두면 대개 속도 대부분 유지하며 정확도 회복. 그래도 부족하면 QAT(5.5).

연습 5 (정밀도 사다리). 새 트랜스포머 기반 추론 서비스의 비용을 낮추라는 요구다. FP16에서 시작해 어떤 순서로 정밀도를 내릴지, 각 단계의 검증·중단 기준과 함께 설계하라.

정답

① FP16/BF16 기준 확보(정확도·지연·처리량·$ 측정). ② FP8 우선 검토(Hopper+ 세대면) — 트랜스포머는 INT8보다 FP8이 정확도·속도 균형이 좋은 경우가 많음(5.5·4.2). 대표 평가셋으로 태스크 지표 비교, 허용 하락 안이면 채택. ③ FP8이 불가/부족하면 INT8 PTQ + 캘리브레이션 → 태스크 지표 검증. ④ 정확도 부족분은 혼합정밀(민감 레이어 고정밀) → 그래도 안 되면 QAT. 각 단계 중단 기준: 태스크 지표 하락이 허용선(예: −1%p) 초과면 그 단계 롤백 또는 혼합정밀로 보정. 모든 결정은 그 하드웨어·그 모델·그 평가셋 실측으로, 슬라이드 수치 금지.

5.4.13 요약 · 다음 장

  • INT8 PTQ는 재학습 없이 캘리브레이션으로 스케일만 정해 메모리 1/4(vs FP32)·처리량↑를 얻는다. 대가는 정밀도 — 반드시 태스크 지표로 검증.
  • 양자화 = q = round(clip(x,−t,t)/scale), scale=t/127. t(동적 범위) 선택이 캘리브레이션의 전부 — 클리핑 vs 해상도의 균형. 가중치 per-channel·활성화 per-tensor.
  • 캘리브레이션은 대표 데이터로 활성화 분포를 모아 t를 정한다. KL/엔트로피(분포 보존)가 기본 계열, minmax·percentile은 상황별. 데이터는 실제 분포·동일 전처리·수백~수천 장·라벨 불필요.
  • 실습: calibrator 구현·연결 → INT8 빌드 → 캘리브레이션 캐시 재사용 → 태스크 지표 검증 → 부족하면 민감 레이어 혼합정밀. PTQ로 부족하면 QAT.
  • 정직: API·클래스는 버전 의존, --int8 단독은 정확도 비보장, 모든 수치 실측, 검증은 분리된 평가셋으로.

다음 장: 5.5 TensorRT 정밀도 II — QAT·FP8·정확도 검증. PTQ로 부족할 때의 QAT(학습 인지 양자화) — 학습 그래프에 가짜양자화(Q/DQ)를 넣어 모델이 양자화에 적응하게 하는 법, FP8(Hopper+ 트랜스포머)과 INT8의 선택, 그리고 정확도 검증을 체계화한다.