[llm-compressor] Pipeline Registry: Modifier 목록을 보고 파이프라인 자동 선택
들어가며
llm-compressor에는 네 종류의 캘리브레이션 파이프라인이 있다. basic, sequential, data_free, independent. 어떤 Modifier를 쓰느냐에 따라 적합한 파이프라인이 다르다. GPTQ는 sequential만 쓸 수 있고, 단순 FP8 weight-only는 data_free로 충분하다. 이 "어떤 파이프라인을 써야 하는가"를 결정하는 곳이 src/llmcompressor/pipelines/registry.py의 CalibrationPipeline 추상 클래스다.
핵심 구조/코드 분석
CalibrationPipeline: 추상 클래스 + 레지스트리
class CalibrationPipeline(ABC, RegistryMixin):
@staticmethod
@abstractmethod
def __call__(
model: torch.nn.Module, # 캘리브레이션 대상 모델
dataloader: DataLoader, # 캘리브레이션 데이터 반복자
dataset_args: "DatasetArguments", # 파이프라인 추가 설정
):
raise NotImplementedError()
RegistryMixin은 compressed_tensors.registry에서 가져온 믹스인으로, 자식 클래스가 @CalibrationPipeline.register("basic") 같은 데코레이터로 등록되면 이름으로 검색 가능해진다. 각 파이프라인 구현체(BasicPipeline, SequentialPipeline 등)는 모두 이 패턴으로 등록된다. __call__은 @staticmethod로 정의되어 인스턴스 상태를 가지지 않는다. 이는 파이프라인이 순수 함수에 가깝다는 것을 의미한다.
from_modifiers: 자동 선택 로직
@classmethod
def from_modifiers(
cls, modifiers: list[Modifier], user: str | None = None
) -> "CalibrationPipeline":
user = standardize_lookup_name(user) if user else None # "Sequential" → "sequential"
inferred = standardize_lookup_name(cls._infer_pipeline(modifiers))
independent = standardize_lookup_name("independent")
# independent 는 사용자 명시 시에만 강제 적용
if user == independent:
inferred = independent
# 사용자와 추론이 불일치하면 경고 로깅 (에러는 아님)
if user is not None and user != inferred:
logger.warning(
f"Calibration pipeline is set to `{user}`, but it is recommended to "
f"use `{inferred}`"
)
pipeline = user or inferred
return cls.load_from_registry(pipeline)
| 시나리오 | 결과 |
|---|---|
| user=None, modifiers에 GPTQ 포함 | inferred = "sequential", 반환 = SequentialPipeline |
| user="basic", modifiers에 GPTQ 포함 | 경고 로깅, 반환 = BasicPipeline (사용자 선택 존중) |
| user="independent", modifiers에 GPTQ 포함 | inferred 무시하고 Independent 강제 |
| user=None, 단일 QuantizationModifier (weight-only) | inferred = "datafree" |
핵심은 **"추천은 하지만 강제하지 않는다"**는 정책이다. 사용자가 명시적으로 파이프라인을 지정하면 llm-compressor는 그 선택을 존중하고, 단지 경고만 남긴다. 전문 사용자가 이유가 있어 특정 파이프라인을 고집할 수 있기 때문이다.
_infer_pipeline: 추론 규칙
@staticmethod
def _infer_pipeline(modifiers: list[Modifier]) -> str:
# weight-only qmod 단일 Modifier 면 data-free 로 충분
if len(modifiers) == 1 and isinstance(modifiers[0], QuantizationModifier):
config = modifiers[0].resolve_quantization_config()
if not config.requires_calibration_data():
return "datafree"
return "sequential"
추론 규칙은 매우 단순하다. 단 하나의 조건이 걸린다.
- Modifier가 정확히 하나이고, 그것이
QuantizationModifier이며, 그 설정이 캘리브레이션 데이터를 필요로 하지 않으면 →datafree파이프라인 - 그 외 모든 경우 →
sequential파이프라인
즉 기본값은 **sequential**이다. basic 파이프라인은 추론 결과로 선택되지 않고, 사용자가 명시적으로 pipeline="basic"을 넘겨야만 쓰인다. 이는 "잘 모르겠으면 안전하게 sequential을 쓰자"는 보수적 정책이다. sequential은 모든 알고리즘과 호환되며 메모리 관점에서도 최적이다.
config.requires_calibration_data()는 Compressed Tensors의 QuantizationConfig가 제공하는 메서드로, "이 스킴이 활성화 통계를 필요로 하는가"를 판단한다. 예를 들어 W8A8(가중치와 활성화 둘 다 양자화)는 True이고, W8A16(가중치만 양자화)은 False다.
네 가지 파이프라인의 성격
| 파이프라인 | 포워드 루프 | 메모리 | 주요 용도 |
|---|---|---|---|
| basic | 전체 모델을 한 번의 forward로 통과 | 전체 모델이 GPU에 | 작은 모델 빠른 캘리브레이션 |
| sequential | 레이어 단위로 쪼개서 순차 forward, 중간 활성화 오프로드 | 한 레이어 분량만 GPU | 대형 모델, GPTQ/AWQ/SparseGPT |
| data_free | 데이터 순회 없음, Modifier의 변환만 적용 | 전체 모델 GPU | FP8 weight-only |
| independent | 각 Modifier마다 자체 파이프라인을 따로 실행 | 다양 | 멀티 알고리즘 |
Independent Pipeline (같은 글에서 함께 다룸)은 특별하다. 레시피에 여러 Modifier가 있을 때, 각 Modifier에 대해 개별적으로 from_modifiers를 호출해 최적 파이프라인을 찾고, 순차로 실행한다. 즉 "각자 좋아하는 방식으로 돌려라". 레시피의 첫 Modifier는 data_free로 돌리고, 다음은 sequential로 돌릴 수 있다.
standardize_lookup_name과 레지스트리
standardize_lookup_name은 compressed_tensors.registry의 유틸리티로, 사용자 입력 문자열을 정규화한다. 예를 들어 "Sequential", "sequential", "SequentialPipeline" 모두 "sequential"로 표준화된다. 이 정규화 덕분에 사용자가 대소문자나 접미사로 실수해도 파이프라인이 올바르게 찾아진다.
레지스트리 등록은 각 파이프라인 파일에서 데코레이터로 이뤄진다.
@CalibrationPipeline.register("basic")
class BasicPipeline(CalibrationPipeline):
...
@CalibrationPipeline.register("sequential")
class SequentialPipeline(CalibrationPipeline):
...
@CalibrationPipeline.register("datafree")
class DataFreePipeline(CalibrationPipeline):
...
@CalibrationPipeline.register("independent")
class IndependentPipeline(CalibrationPipeline):
...
Python import 시점에 등록이 일어나므로, from llmcompressor.pipelines import CalibrationPipeline 한 줄만 있어도 네 파이프라인이 모두 로드된다. pipelines/__init__.py가 각 모듈을 import해 등록을 트리거한다.
왜 이 설계인가
1. 자동 추론 + 사용자 명시 허용. 초보 사용자는 파이프라인을 신경 쓸 필요가 없고, 전문 사용자는 명시적 제어를 유지한다. 둘의 균형을 from_modifiers의 사용자 인자로 해결한다.
2. 데이터 필요 여부로 data_free 판별. 단일 Modifier + config.requires_calibration_data()==False라는 매우 구체적 조건으로만 data_free를 추천한다. 이 조건이 조금이라도 틀어지면 sequential로 fallback해 안전성을 확보한다.
3. 보수적 기본값 sequential. Basic 파이프라인은 추론으로 선택되지 않는다. 이는 "basic은 메모리 여유가 있는 작은 모델에만 안전하다"는 판단이다. 기본값을 sequential로 두면 어떤 모델에서도 동작한다.
4. RegistryMixin 재사용. compressed-tensors의 레지스트리 믹스인을 빌려와서 파이프라인 레지스트리를 만든다. llm-compressor 전용 코드를 추가하지 않고 기존 인프라를 활용한다.
5. staticmethod __call__. 파이프라인이 인스턴스 상태를 가지지 않아, 한 번 등록된 클래스가 여러 번 재사용되어도 부작용이 없다. CalibrationPipeline.load_from_registry("sequential")는 매번 같은 클래스를 반환한다.
마무리
Pipeline Registry는 "어떤 Modifier에 어떤 실행 방식을 붙일 것인가"를 자동화한다. 사용자는 "이 레시피가 무엇을 필요로 하는가"를 고민할 필요가 없다. 다음 글은 이 레지스트리에 등록된 첫 번째 구현체인 Basic Pipeline을 본다.
참고 자료
관련 포스트
- [llm-compressor] Modifier Factory: 문자열 이름에서 Modifier 인스턴스 생성
- [llm-compressor] Intermediates Cache: 서브그래프 활성화 오프로드 캐시
- [llm-compressor] Data-Free & Independent Pipeline: 데이터 없는 파이프라인과 Modifier별 개별 실행
- [llm-compressor] Sequential Pipeline: 레이어 단위 서브그래프 캘리브레이션
- [llm-compressor] Basic Pipeline: 한 번의 forward로 끝내는 캘리브레이션
llm-compressor 의 다른글
- 이전글 [llm-compressor] Events: 배치 라이프사이클 훅과 에폭 계산 로직
- 현재글 : [llm-compressor] Pipeline Registry: Modifier 목록을 보고 파이프라인 자동 선택
- 다음글 [llm-compressor] Basic Pipeline: 한 번의 forward로 끝내는 캘리브레이션
댓글