[llm-compressor] Data-Free & Independent Pipeline: 데이터 없는 파이프라인과 Modifier별 개별 실행
들어가며
지금까지 본 Basic Pipeline과 Sequential Pipeline은 모두 캘리브레이션 데이터를 forward로 순회한다. 하지만 어떤 Modifier들은 데이터가 전혀 필요 없다. FP8 weight-only, 단순 min-max 기반 INT8 weight-only 같은 스킴은 가중치 분포만 보고 스케일을 결정할 수 있다. 이런 경우를 위한 것이 DataFreePipeline이다.
또 하나 흥미로운 파이프라인은 IndependentPipeline이다. 레시피에 서로 다른 성격의 Modifier가 여러 개 있을 때, 각 Modifier를 자체 파이프라인으로 따로 돌린다. 이 글에서 두 파이프라인을 함께 다룬다.
DataFreePipeline 분석
src/llmcompressor/pipelines/data_free/pipeline.py의 코드는 정말로 짧다.
@CalibrationPipeline.register("datafree")
class DataFreePipeline(CalibrationPipeline):
@staticmethod
def __call__(
model: torch.nn.Module,
dataloader: Optional[DataLoader], # None 이어도 상관 없음
dataset_args: "DatasetArguments",
):
# Modifier 가 가중치를 만질 수 있도록 GPU 에 배치
dispatch_model(model)
# 두 이벤트만 발생 — 루프 없음
LifecycleCallbacks.calibration_epoch_start()
LifecycleCallbacks.calibration_epoch_end()
| 호출 | 의미 |
|---|---|
dispatch_model(model) |
모델을 자동으로 디바이스에 배치. GPU 가능하면 GPU |
CALIBRATION_EPOCH_START 이벤트 |
Modifier의 on_initialize 이후 호출되는 훅 |
CALIBRATION_EPOCH_END 이벤트 |
Modifier에게 "캘리브레이션 끝났다, 최종화해라" 신호 |
포워드 루프가 없다. 데이터로더가 None이어도 파이프라인이 돈다. 실제 작업은 이벤트 훅을 받은 Modifier가 수행한다. 예를 들어 weight-only FP8 QuantizationModifier는 on_finalize에서 각 Linear 모듈의 가중치 max-abs를 계산하고 스케일을 결정한 뒤 양자화된 텐서로 교체한다. 데이터가 필요 없다.
언제 자동 선택되는가
Pipeline Registry의 _infer_pipeline을 다시 보면,
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"
조건은 단 하나: 단일 QuantizationModifier + 캘리브레이션 데이터 불필요 설정. W8A8(가중치와 활성화 둘 다)은 활성화 통계가 필요하므로 requires_calibration_data()==True라서 data_free가 선택되지 않는다. 반면 W8A16, FP8_WEIGHT_ONLY, NVFP4_WEIGHT 같은 스킴은 False라서 data_free가 추천된다.
IndependentPipeline 분석
src/llmcompressor/pipelines/independent/pipeline.py는 조금 더 흥미롭다. 이 파이프라인은 자신이 직접 캘리브레이션을 하지 않고, 각 Modifier를 개별 파이프라인으로 돌린다.
@CalibrationPipeline.register("independent")
class IndependentPipeline(CalibrationPipeline):
@staticmethod
def __call__(
model: torch.nn.Module,
dataloader: DataLoader,
dataset_args: "DatasetArguments",
):
_logger = logger.patch(lambda r: r.update(function="IndependentPipeline"))
session = active_session()
modifiers = session.lifecycle.recipe.modifiers
# 현재 레시피의 modifiers 필드를 일시적으로 None 으로 패치
with patch_attr(session.lifecycle.recipe, "modifiers", None):
for modifier in modifiers:
mod_type = type(modifier).__name__
session.lifecycle.recipe.modifiers = [modifier] # 이 Modifier 하나만 활성
# 이 Modifier 에 맞는 파이프라인을 재귀적으로 찾는다
pipeline = CalibrationPipeline.from_modifiers([modifier])
pipeline_name = pipeline.__class__.__name__
_logger.info(f"Inferred `{pipeline_name}` for `{mod_type}`")
pipeline(model, dataloader, dataset_args)
# 컨텍스트 종료 시 원래 modifiers 리스트 복원
각 Modifier 하나만 활성인 상태로 from_modifiers를 호출한다. 그러면 레지스트리가 그 Modifier 하나에 맞는 최적 파이프라인을 고른다. 예를 들어 레시피가 [SmoothQuantModifier, GPTQModifier]라면, Independent는 다음과 같이 돈다.
- SmoothQuant 만 활성 →
from_modifiers가sequential반환 (SmoothQuant는 활성화 통계가 필요) → sequential로 실행 - GPTQ 만 활성 →
from_modifiers가sequential반환 → sequential로 실행
패치 컨텍스트 매니저 patch_attr(session.lifecycle.recipe, "modifiers", None)는 종료 시 원래 리스트를 복원한다. 이렇게 해야 나중에 Compression Save 단계에서 전체 레시피 기반으로 가중치를 직렬화할 수 있다.
언제 Independent를 쓰는가
independent는 추론으로 선택되지 않는다. 사용자가 oneshot(..., pipeline="independent")로 명시해야 한다. 흥미롭게도 oneshot()의 pipeline 기본값은 "independent"다. 즉 사용자는 기본적으로 Independent를 받는다.
이 기본값은 타당하다. Independent는 "레시피의 각 Modifier에게 가장 어울리는 파이프라인을 골라준다"는 가장 안전한 선택이기 때문이다. GPTQ 하나만 있으면 sequential로, FP8 weight-only 하나만 있으면 data_free로, 복합 레시피면 각자에게 맞게 분할해 실행한다.
네 파이프라인 비교 표
| 파이프라인 | 포워드 | 데이터 필요 | 대표 Modifier |
|---|---|---|---|
basic |
1회 | 필요 | SmoothQuant (명시적 선택 시) |
sequential |
서브그래프별 2회 | 필요 | GPTQ, AWQ, SparseGPT, AutoRound |
data_free |
없음 | 불필요 | FP8 weight-only, W8A16 |
independent |
Modifier별로 달라짐 | 혼합 | 멀티 Modifier 레시피 |
왜 이 설계인가
1. data_free의 최소주의. 포워드 루프가 없으므로 수십 라인에 끝난다. Modifier 의 on_initialize/on_finalize 훅이 전부를 담당한다. 가장 빠른 파이프라인이자 가장 단순한 인터페이스.
2. independent의 자동 분해. 사용자가 복합 레시피를 만들어도 파이프라인 선택을 신경 쓸 필요가 없다. 각 Modifier가 자기에게 최적인 파이프라인을 받는다. "잘 모르겠으면 independent를 써라"가 llm-compressor의 권장이다.
3. patch_attr로 안전한 상태 교체. Independent는 session.lifecycle.recipe.modifiers를 일시적으로 한 Modifier 리스트로 교체한다. 컨텍스트 매니저가 종료 시 원복을 보장하므로 예외 발생 시에도 안전하다.
4. Optional[DataLoader] 시그니처. DataFree는 dataloader=None이어도 동작한다. 이 유연성 덕에 사용자가 데이터 없이 oneshot()을 호출해도 에러가 나지 않고, 자연스럽게 data_free 경로로 흐른다.
5. 일관된 CALIBRATION_EPOCH_* 이벤트. 네 파이프라인 모두 같은 이벤트 쌍을 호출한다. Modifier 입장에서는 "어떤 파이프라인에서 실행되는지" 신경 쓸 필요가 없고, 자신이 훅을 받는 순간에 필요한 일을 하면 된다.
마무리
Data-Free와 Independent는 파이프라인 시스템의 양 끝을 보여준다. 하나는 "아무것도 안 해도 되는" 최소주의, 다른 하나는 "각 Modifier가 자기 방식대로"의 메타 파이프라인이다. 다음 글은 Sequential Pipeline이 의존하는 Intermediates Cache를 본다.
참고 자료
관련 포스트
llm-compressor 의 다른글
- 이전글 [llm-compressor] Sequential Pipeline: 레이어 단위 서브그래프 캘리브레이션
- 현재글 : [llm-compressor] Data-Free & Independent Pipeline: 데이터 없는 파이프라인과 Modifier별 개별 실행
- 다음글 [llm-compressor] Intermediates Cache: 서브그래프 활성화 오프로드 캐시
댓글