본문 바로가기
Artificial Intelligence/60. Python

[PYTHON] TensorRT FP16 양자화 오차를 해결하는 3가지 Calibration 데이터 선정 방법

by Papa Martino V 2026. 4. 20.
728x90

TensorRT FP16
TensorRT FP16

 

딥러닝 모델을 실무 환경, 특히 NVIDIA GPU 기반의 엣지 디바이스나 클라우드 서버에 배포할 때 TensorRT는 선택이 아닌 필수입니다. 하지만 단순히 모델을 FP16(Half Precision)으로 변환한다고 해서 모든 문제가 해결되지는 않습니다. 특정 도메인(의료, 정밀 제조, 자율주행)에서는 아주 미세한 양자화 오차가 모델의 신뢰성을 무너뜨리기도 합니다. 본 포스팅에서는 Python 환경에서 TensorRT 최적화 시 FP16 및 INT8 양자화 과정에서 발생하는 오차를 최소화하기 위한 전략적인 Calibration 데이터 선정 알고리즘과 실무 코드를 깊이 있게 다룹니다. 1%의 정확도 손실도 허용하지 않는 시니어 엔지니어를 위한 가이드를 확인해 보세요.


1. FP16 양자화와 Calibration의 상관관계

FP16은 부동소수점 표현 범위를 줄여 연산 속도를 약 2배 이상 향상시키지만, 지수부가 5비트로 제한되어 있어 값의 범위가 $6.10 \times 10^{-5}$에서 $65,504$ 사이로 좁아집니다. 이 과정에서 발생하는 Precision Loss를 방지하기 위해 TensorRT는 내부적으로 스케일링을 수행하며, 특히 INT8로의 하이브리드 변환까지 고려한다면 '어떤 데이터를 통해 분포를 파악(Calibration)하느냐'가 모델의 운명을 결정합니다.

왜 Calibration 데이터 선정이 중요한가?

  • Outlier 대응: 학습 데이터 전체를 사용할 수 없는 배포 환경에서 이상치(Outlier)가 포함된 소수의 데이터로 Calibration을 수행하면 전체 활성화 함수(Activation)의 범위를 왜곡시킵니다.
  • 비대칭 분포 해결: ReLU와 같은 활성화 함수 이후의 데이터는 0에 편향된 비대칭 분포를 가집니다. 이를 무시하고 Calibration 세트를 구성하면 하향 편향(Negative Bias) 오차가 누적됩니다.

2. Calibration 데이터 선정 및 알고리즘 비교

실무에서 가장 많이 사용되는 3가지 Calibration 전략을 비교 분석하였습니다.

전략 구분 핵심 알고리즘 장점 단점 추천 적용 분야
Entropy Minimization Kullback-Leibler Divergence (KLD) 정보 손실을 수학적으로 최소화 연산량이 많고 분포가 복잡할 때 느림 이미지 분류, 일반 객체 인식
Percentile Strategy 99.99% Clipping 이상치(Outlier) 제거에 탁월함 중요한 극값 정보가 누락될 수 있음 의료 영상, 미세 결함 탐지
Min-Max Calibration Floating Point Range Mapping 구현이 매우 단순하고 빠름 특이값에 의한 범위 확장 왜곡 심함 실시간 스트리밍, 단순 회귀 모델

3. [Practical Examples] 실무 적용을 위한 Python 개발자 가이드

아래 예제들은 TensorRT Python API를 활용하여 실제 프로젝트에서 즉시 활용 가능한 구조로 설계되었습니다.

Example 1: 기본 Entropy Calibrator 구조 설계

import tensorrt as trt
import os

class MyEntropyCalibrator(trt.IInt8EntropyCalibrator2):
    def __init__(self, training_data_path, cache_file):
        super().__init__()
        self.cache_file = cache_file
        # 데이터 로드 로직 (Representative Dataset)
        self.data = self.load_representative_data(training_data_path)
        self.batch_size = 32
        self.current_index = 0
        self.device_input = cuda.mem_alloc(self.data[0].nbytes * self.batch_size)

    def get_batch(self, names):
        if self.current_index + self.batch_size > len(self.data):
            return None
        batch = self.data[self.current_index:self.current_index + self.batch_size]
        cuda.memcpy_htod(self.device_input, batch)
        self.current_index += self.batch_size
        return [self.device_input]

Example 2: 오차 최소화를 위한 데이터 셔플링 및 다양성 확보

import numpy as np

def select_diverse_calibration_data(full_dataset, num_samples=500):
    # 단순 랜덤 추출이 아닌 클래스별 균형 추출
    labels = full_dataset.labels
    unique_labels = np.unique(labels)
    samples_per_class = num_samples // len(unique_labels)
    
    calib_indices = []
    for label in unique_labels:
        idx = np.where(labels == label)[0]
        selected = np.random.choice(idx, samples_per_class, replace=False)
        calib_indices.extend(selected)
    
    return full_dataset[calib_indices]

Example 3: TensorRT Builder 설정에서의 FP16 모드 활성화

def build_engine(onnx_file_path, engine_file_path, calibrator):
    logger = trt.Logger(trt.Logger.INFO)
    builder = trt.Builder(logger)
    config = builder.create_builder_config()
    
    # FP16 플래그 설정 (양자화 오차 대응의 핵심)
    if builder.platform_has_fast_fp16:
        config.set_flag(trt.BuilderFlag.FP16)
    
    config.int8_calibrator = calibrator
    config.set_flag(trt.BuilderFlag.INT8) # 하이브리드 최적화
    
    # ... ONNX 파싱 및 엔진 빌드 로직

Example 4: Histogram 분석을 통한 Optimal Clip Value 산출

def find_optimal_threshold(activations, percentile=99.99):
    # Activation 값들의 분포를 분석하여 FP16 범위 내의 최적 임계값 설정
    threshold = np.percentile(np.abs(activations), percentile)
    return threshold

Example 5: 다중 입력 모델을 위한 Multi-Stream Calibrator

# 여러 개의 입력을 가진 모델(예: 멀티모달)의 경우 각 스트림별 데이터 정렬
def get_batch(self, names):
    # names: ['input_image', 'input_metadata']
    img_batch = self.images[self.idx : self.idx + self.bs]
    meta_batch = self.meta[self.idx : self.idx + self.bs]
    
    cuda.memcpy_htod(self.d_input_img, img_batch)
    cuda.memcpy_htod(self.d_input_meta, meta_batch)
    
    return [self.d_input_img, self.d_input_meta]

Example 6: Calibration Cache 생성 및 로드 (시간 단축 기법)

def read_calibration_cache(self):
    if os.path.exists(self.cache_file):
        with open(self.cache_file, "rb") as f:
            return f.read()
    return None

def write_calibration_cache(self, cache):
    with open(self.cache_file, "wb") as f:
        f.write(cache)

Example 7: 양자화 전후 코사인 유사도(Cosine Similarity) 측정

from sklearn.metrics.pairwise import cosine_similarity

def check_quantization_error(original_output, trt_output):
    # FP32 모델과 FP16/INT8 TRT 엔진의 출력 결과 비교
    sim = cosine_similarity(original_output.reshape(1, -1), trt_output.reshape(1, -1))
    print(f"Quantization Fidelity (Cosine Similarity): {sim[0][0]:.4f}")
    return sim[0][0]

4. 결론: 성공적인 Calibration을 위한 3가지 체크리스트

  1. 데이터의 양보다는 질: 1000개의 중복된 데이터보다 도메인을 대표하는 100개의 유니크한 데이터가 오차를 더 잘 줄입니다.
  2. Batch Size의 일치: Calibration 시의 배치 사이즈를 실제 추론(Inference) 환경과 최대한 동일하게 구성하십시오.
  3. 통계적 검증: 위 Example 7에서 제시한 코사인 유사도 측정을 통해 오차가 0.98 이하로 떨어질 경우 Calibration 데이터를 전면 재검토해야 합니다.

참고 문헌 및 출처

  • NVIDIA Developer Blog: "Int8 Calibration with TensorRT" (2024-2025)
  • TensorRT Documentation: "Optimizing Layer Precision"
  • Deep Learning Performance Guide: "FP16 vs INT8 Quantization Strategies"
  • ArXiv: "Data-Free Quantization through Weight Equalization and Bias Correction"
728x90