본문으로 건너뛰기

[llm-compressor] Modifier Interface: 추상 계약과 타입 체크

들어가며

Modifier Base 글에서 본 Modifier 클래스는 두 가지를 상속한다. 하나는 HooksMixin(forward hook 관리), 다른 하나는 ModifierInterface다. 이 ModifierInterface는 매우 작은 ABC(Abstract Base Class)로, "Modifier가 반드시 노출해야 할 인터페이스"만 선언한다. 실제 구현은 전혀 없다. 이 글은 src/llmcompressor/modifiers/interface.py의 45줄을 해부하고, 왜 별도 인터페이스 레이어가 필요한지를 살펴본다.

핵심 구조/코드 분석

ModifierInterface: 순수 추상 클래스

class ModifierInterface(ABC):
    """Defines the contract that all modifiers must implement"""

    @property
    @abstractmethod
    def initialized(self) -> bool:
        """초기화되었는지 여부"""
        raise NotImplementedError()

    @property
    @abstractmethod
    def finalized(self) -> bool:
        """종료되었는지 여부"""
        raise NotImplementedError()

    @abstractmethod
    def initialize(self, state: State, **kwargs):
        """Modifier 초기화 — 자식이 반드시 구현"""
        raise NotImplementedError()

    @abstractmethod
    def finalize(self, state: State, **kwargs):
        """Modifier 종료 — 자식이 반드시 구현"""
        raise NotImplementedError()

    @abstractmethod
    def update_event(self, state: State, event: Event, **kwargs):
        """이벤트 기반 업데이트 — 자식이 반드시 구현"""
        raise NotImplementedError()
추상 멤버 타입 역할
initialized property initialize()가 성공했는지
finalized property finalize()가 성공했는지
initialize(state, **kwargs) method 세션 초기화 시 호출
finalize(state, **kwargs) method 세션 종료 시 호출
update_event(state, event, **kwargs) method 배치 이벤트 수신

ABC@abstractmethod 데코레이터 덕분에, 이 다섯 멤버를 모두 구현하지 않은 클래스는 인스턴스화할 수 없다. 런타임에 TypeError: Can't instantiate abstract class가 뜬다.

Base와 Interface의 관계

Modifier BaseModifier 클래스는 이 인터페이스를 모두 구현한다.

# src/llmcompressor/modifiers/modifier.py
class Modifier(ModifierInterface, HooksMixin):
    initialized_: bool = False   # 실제 상태
    finalized_: bool = False

    @property
    def initialized(self) -> bool:     # ← ModifierInterface 요구사항 충족
        return self.initialized_

    @property
    def finalized(self) -> bool:        # ← ModifierInterface 요구사항 충족
        return self.finalized_

    def initialize(self, state: State, **kwargs):   # ← 구현
        ...

    def finalize(self, state: State, **kwargs):     # ← 구현
        ...

    def update_event(self, state: State, event: Event, **kwargs):   # ← 구현
        ...

즉 모든 llm-compressor 내장 Modifier는 Modifier를 상속하고, Modifier가 이미 ModifierInterface를 구현해 두었으므로, 자식 클래스는 on_initialize만 구현하면 된다.

그럼 왜 이 인터페이스 레이어가 따로 있을까?

왜 이 설계인가

1. 덕 타이핑 방지 + 명시적 계약. 파이썬은 덕 타이핑이 가능하지만, 라이브러리 경계에서는 명시적 계약이 유용하다. ModifierInterface는 "llm-compressor가 Modifier에게 기대하는 다섯 가지 멤버"를 문서처럼 남긴다. isinstance(x, ModifierInterface) 체크가 가능하다.

2. 외부 플러그인 통합 지점. 다른 라이브러리가 자체 Modifier 유사 객체를 llm-compressor 파이프라인에 꽂고 싶다면, Modifier를 상속하는 대신 ModifierInterface만 구현하면 된다. Modifier 베이스는 Pydantic BaseModel이라 무거운 의존성이 있지만, ModifierInterface는 순수 ABC라 외부에서 구현하기 쉽다.

3. 순환 import 회피. Modifier 베이스 클래스는 Event, State, HooksMixin 등을 import한다. 이 의존성 체인이 길어지면 순환 import가 나기 쉽다. ModifierInterface는 최소한의 타입만 import해서, 다른 모듈이 "Modifier 를 참조만 하고 인스턴스를 만들지 않는" 경우에 사용하기 적합하다.

4. 테스트 모킹. 테스트 코드에서 "Modifier 같은 객체"가 필요할 때, 전체 Pydantic 베이스를 상속하는 것은 과하다. ModifierInterface만 구현한 얇은 mock 클래스를 만들어 Lifecycle 테스트를 격리시킬 수 있다.

5. 역할 분리. "계약(Interface)"과 "기본 구현(Base)"을 분리하는 것은 OOP 관습이다. Java의 InterfaceAbstractClass 관계와 비슷하다. 파이썬에서도 ABC를 사용해 이 관습을 따를 수 있다.

마무리

ModifierInterface는 파일 크기로는 가장 작지만, 라이브러리 설계의 의도를 가장 명확하게 보여주는 파일이다. "무엇을 구현해야 하는가"와 "어떻게 구현해야 하는가"의 분리가 모든 Modifier 시스템의 기반이 된다. 다음 글부터는 실제 Quantization Base부터 시작해 각 알고리즘 구현을 파고든다.

참고 자료

댓글

관련 포스트

llm-compressor 의 다른글