HF-Model
- PreTrainedModel 튜토리얼
- 0.
PreTrainedModel이란? - 1. 텍스트(Text)용 PreTrainedModel
- 2. 이미지(Image)용 PreTrainedModel
- 3. Tokenizer와 ImageProcessor를 Processor로 추상화할 수 있는가?
- 4. Processor for Multimodal Models
- 5. AutoModel과의 관계
- 6. 실제 Hub 로부터 복원 동작 방식
- 요약
PreTrainedModel 튜토리얼
Text / Image 의 경우를 나누어서 간단히 소개
0. PreTrainedModel 이란?
PreTrainedModel은 Hugging Face Transformers에서 “모델 본체”의 공통 기능을 제공하는 기반 클래스임.
여기서 “모델 본체”란,
곧, 신경망 module(=layers) + 가중치 로딩/세이브 규약 + HF Hub 호환 인터페이스를 의미함.
PretrainedConfig가 “구조 정의(설계도)”라면,
PreTrainedModel은
- 그 설계도(
config)를 받아서 - 실제 레이어들을 만들고 (
__init__) - 연결하고 (
forward), - 가중치를 붙여서 동작 가능한 모델로 만드는 구현체**임.
모델을 완전히 복원하려면 보통 아래 2가지를 함께 사용함.
- 구조 정의:
config.json(=PretrainedConfig가 직렬화된 결과) - 가중치:
model.safetensors(또는 shard들)
그리고 이 둘(config.json, model.safetensors)을 함께 취급해서
save_pretrained()로 저장하고from_pretrained()로 로드하는
기능을 제공하는 쪽이 PreTrainedModel임.
Config / Model / 입력 객체의 역할 분리
여기서 매우 중요한 구조적 분리가 존재함.
- Config (
PretrainedConfig): 모델 구조를 재현하기 위한 메타데이터(하이퍼파라미터) - Model (
PreTrainedModel): 레이어 구현, forward, 저장/로드 규약(가중치 포함) - 입력 전처리 객체:
- Text:
Tokenizer - Image:
ImageProcessor - Multimodal:
Processor
- Text:
즉, 다음을 기억할 것:
config는 모델이 어떻게 생겼는가processor/tokenizer는 입력이 어떻게 들어와야 하는가model은 실제로 계산을 수행하는 본체
이 역할 분리를 정확히 지키는 것이 HF 생태계에서 재현성과 HF Hub 호환성의 핵심임.
0.1 PreTrainedModel이 실제로 제공하는 기능
PreTrainedModel은 단순한 nn.Module이 아니라,
다음의 기능들을 제공하는 고수준 추상화 모델임.
- Hub 로부터 복원 contract
- Config와 결합 구조
- Auto클래스와 연동
주요 기능은 다음과 같음:
1) Config와의 결합
다음과 같이 PreTrainedModel의 자식 클래스는 config_class를 가짐:
class MyModel(PreTrainedModel):
config_class = MyConfig
- 이 모델이 어떤
PretrainedConfig를 받는지 명시 AutoModel에서 dispatch의 기준이 됨- Hub의
auto_map메타데이터와 연결됨 from_pretrained()가 config 기반으로 올바른 모델 클래스를 선택할 수 있게 함
2) from_pretrained()의 내부 복원 절차
보통은 PreTrainedModel 이 제공하는 메서드를 그냥 사용하면 되지만,
특별한 기능 등이 필요하다면 overridding 가능함.
model = MyModel.from_pretrained("repo_id")
동작 단계:
config.json로드- 해당하는
Config객체 생성 - 모델 인스턴스 생성 (
__init__(config)) - 가중치 파일(
model.safetensors또는 shard) 로드 state_dict매핑 및 로딩- missing / unexpected key 검사
- dtype / device 설정
tie_weights()및 내부 초기화 정리- 기본적으로
eval()모드 설정
즉, from_pretrained()는 단순한 load_state_dict 이 아니라
Hub 의 contract 전체를 복원하는 고수준의 복합 복원 메서드 에 해당함.
3) state_dict 로딩 전략
HF는 일반 PyTorch의 경우와 달리 다음을 처리함:
- missing keys 처리
- unexpected keys 처리
- sharded weight 로딩
- safetensors 지원
low_cpu_mem_usage=True옵션 통해 메모리 최적화 로딩.device_map="auto"기반 분산 로딩dtype자동 캐스팅.
이는 일반 PyTorch 모델의 load_state_dict 와 구조적으로 다른 중요한 차이점임.
4) post_init()의 역할
Custom 모델 작성 시 생성자(__init__)에서 다음의 hook 호출이 필요.:
self.post_init()
이 호출을 통해 수행되는 내용은 다음과 같음:
- weight initialization 수행:
self.init_weights()실행 self.tie_weights()실행- gradient checkpointing 관련 초기화
- 내부 hook 정리
이를 호출하지 않으면 HF 기대 동작과 어긋날 수 있음.
5) 저장 규약
model.save_pretrained("./out")
결과는 다음과 같음:
out/
├── config.json
├── model.safetensors
└── (필요 시 shard 파일들)
Config와 Weight는 항상 분리 저장됨.
0.2 PretrainedConfig와의 구조적 관계
구조-구현 분리 관점에서:
PretrainedConfig : 구조 메타데이터
PreTrainedModel : 실제 레이어 구현
- Config:
- 레이어 수,
- hidden size,
- dropout,
- label mapping,
- model_type
- 등의 구조 및 라벨 관련 선언적 정의 담당.
- Model:
nn.Linear,nn.LayerNorm, backbone 연결,forward등- 실제 실행을 구현 하는 객체.
즉,
Config는 “설계도”, Model은 “설계도를 구현한 실행 객체”.
0.3 nn.Module과의 차이
| 항목 | nn.Module | PreTrainedModel |
|---|---|---|
| config 결합 | 없음 | 강제 결합 |
| Hub 저장 | 수동 구현 | save_pretrained() |
| Hub 복원 | 수동 구현 | from_pretrained() |
| AutoModel 연동 | 불가 | 가능 |
| sharded loading | 없음 | 지원 |
| dtype/device 자동 처리 | 제한적 | 지원 |
따라서,
- HF 생태계에서 모델을 배포하거나 재현 가능하게 만들려면
PreTrainedModel상속이 필수적임.
0.4 Hub 복원 전체 흐름
repo_id
↓
config.json
↓
Config 객체 생성
↓
AutoModel dispatch (또는 명시적 클래스)
↓
PreTrainedModel __init__
↓
model.safetensors 로딩
↓
완전 복원된 모델
Processor는 별도의 경로로 복원되며, 모델 내부에 포함되지 않음.
1. 텍스트(Text)용 PreTrainedModel
텍스트 모델은 보통
- 입력:
input_ids,attention_mask(필요 시token_type_ids) - 출력:
logits또는BaseModelOutput계열
형태를 가짐.
1.1 텍스트에서 Tokenizer의 역할
Tokenizer는 텍스트에서의 전처리 객체임.
이미지에서의 ImageProcessor 와 동일한 개념적 역할을 수행함.
다음의 기능을 제공:
- 문자열을 subword token 단위로 분해
- 각 token을 vocabulary index로 매핑
- special token 들 추가 삽입
- padding 수행
- attention mask 생성
- segment id 생성(필요 시)
예:
tokens = tokenizer("Hello world", return_tensors="pt")
- 이때 반환되는 것은 이미 모델이 처리 가능한 텐서 구조임.
- Model은 문자열을 직접 처리하지 않음.
즉,
- Tokenizer는 입력 규격 정의 및 변환 수행 객체 이고
- Model은 "계산 객체"임.
1.2 최소 예제: 커스텀 텍스트 분류 모델
import torch
import torch.nn as nn
from transformers import PreTrainedModel, AutoModel
from transformers.modeling_outputs import SequenceClassifierOutput
class MyTextForSequenceClassification(PreTrainedModel):
config_class = MyTextConfig
def __init__(self, config: MyTextConfig):
super().__init__(config)
self.backbone = AutoModel.from_pretrained(config.backbone_name_or_path)
hidden_size = self.backbone.config.hidden_size
self.classifier = nn.Linear(hidden_size, config.num_labels)
self.post_init()
def forward(self, input_ids=None, attention_mask=None, labels=None, **kwargs):
out = self.backbone(input_ids=input_ids, attention_mask=attention_mask, **kwargs)
pooled = out.last_hidden_state[:, 0]
logits = self.classifier(pooled)
loss = None
if labels is not None:
loss = nn.CrossEntropyLoss()(logits, labels)
return SequenceClassifierOutput(loss=loss, logits=logits)
여기서 중요한 점은 다음과 같음:
config_class = MyTextConfig로 "이 모델은 이 Config를 받는다"를 명시super().__init__(config)호출로 HF 내부 규약 초기화post_init()호출로 HF가 기대하는 초기화 루틴 정리
Tokenizer는 모델 내부에 포함되지 않음.
전처리는 항상 외부에서 수행됨.
1.3 텍스트 모델에서 ModelOutput 사용 이유
위 예제에서:
return SequenceClassifierOutput(loss=loss, logits=logits)
을 사용한 이유는:
HF Trainer가 다음 키를 기대하기 때문임:
losslogitshidden_statesattentions
ModelOutput은 다음의 특징을 가짐:
dict처럼 접근 가능attribute접근 가능tuple처럼 언패킹 가능
즉,
ModelOutput은
Trainer / pipeline / AutoModel 계층과의 인터페이스를 맞추기 위한 구조임.
2. 이미지(Image)용 PreTrainedModel
이미지 모델은 보통
- 입력:
pixel_values(B,C,H,W) - 출력:
logits또는ImageClassifierOutput
형태를 가짐.
2.1 ImageProcessor의 역할
ImageProcessor는 이미지에서의 전처리 객체임.
수행하는 일:
- resize
- crop
- normalize
- tensor 변환
- 채널 순서 정리
- batch dimension 처리
예:
inputs = processor(image, return_tensors="pt")
출력:
pixel_values
이미지의 경우도 Model은 이미지 원본을 직접 처리하지 않음.
2.2 최소 예제: 커스텀 이미지 분류 모델
from transformers.modeling_outputs import ImageClassifierOutput
class MyImageForImageClassification(PreTrainedModel):
config_class = MyImageConfig
def __init__(self, config: MyImageConfig):
super().__init__(config)
self.backbone = AutoModel.from_pretrained(config.backbone_name_or_path)
hidden_size = getattr(self.backbone.config, "hidden_size", None)
if hidden_size is None:
raise ValueError("backbone.config.hidden_size 를 찾을 수 없습니다.")
self.classifier = nn.Linear(hidden_size, config.num_labels)
self.post_init()
def forward(self, pixel_values=None, labels=None, **kwargs):
out = self.backbone(pixel_values=pixel_values, **kwargs)
pooled = out.last_hidden_state[:, 0]
logits = self.classifier(pooled)
loss = None
if labels is not None:
loss = nn.CrossEntropyLoss()(logits, labels)
return ImageClassifierOutput(loss=loss, logits=logits)
2.3 이미지 모델에서 Backbone 설계 시 고려사항
이미지 모델의 경우 backbone 종류가 다양함:
- ViT (Transformer 기반)
- CNN (ResNet, DenseNet 등)
- Hybrid
따라서 다음의 방식이 항상 동작한다고 기대하기 어려움:
hidden_size = getattr(self.backbone.config, "hidden_size", None)
CNN의 경우:
- feature map flatten 필요
- global pooling 필요
- config에 hidden_size가 없을 수 있음
즉, Image 모델 wrapper는 backbone 출력 규격을 정확히 이해해야 함.
이는 Text 모델보다 구현 난이도가 높음.
3. Tokenizer와 ImageProcessor를 Processor로 추상화할 수 있는가?
3.1 개념적 구조
공통 구조:
Raw Input → Processor → Tensor → Model
Text:
문자열 → Tokenizer → input_ids → Model
Image:
이미지 → ImageProcessor → pixel_values → Model
- Tokenzier와 ImageProcessor 둘은 모두 "전처리 객체"라는 동일한 개념적 역할을 수행함.
- 이는 Processor 라는 클래스로 추상화됨.
3.2 실제 구현 클래스들
구현 계층에서는 차이점이 존재함:
| 항목 | Tokenizer | ImageProcessor |
|---|---|---|
| 기반 클래스 | PreTrainedTokenizerBase |
ImageProcessingMixin |
| 내부 알고리즘 | BPE/WordPiece | Resize/Normalize |
| 출력 키 | input_ids |
pixel_values |
따라서:
- 추상 개념으로는 Processor로 묶을 수 있음
- 구현 레벨에서는 완전히 다른 클래스 사용.
3.3 Processor와 Model은 절대 결합하면 안 되는 이유
중요 설계 원칙:
- Processor는 입력 규격을 정의
- Model은 계산을 정의
Model 내부에 Tokenizer나 ImageProcessor를 넣으면:
- 저장 시 Hub 구조가 깨짐
- 재현성 저하
- AutoProcessor와 충돌
따라서 HF 구조는 의도적으로 분리되어 있음.
4. Processor for Multimodal Models
대표 예:
- CLIP (Contrastive Language-Image Pretraining)
"openai/clip-vit-base-patch32"
CLIP은 다음으로 구성됨:
- Text encoder
- Vision encoder
- 통합 Processor
4.1 CLIPProcessor 내부 구조
class CLIPProcessor(ProcessorMixin):
def __init__(self, tokenizer, image_processor):
self.tokenizer = tokenizer
self.image_processor = image_processor
즉,
Processor는 실제 전처리를 구현한다기보다
여러 전처리 객체를 보유하는 wrapper임.
4.2 Hub 저장 구조
repo/
├── config.json
├── model.safetensors
├── tokenizer.json
├── tokenizer_config.json
├── special_tokens_map.json
├── preprocessor_config.json
├── processor_config.json
processor_config.json은 다음의 정보를 가짐:
- 어떤 tokenizer
- 어떤 image_processor
- 어떤 auto_map
4.3 AutoProcessor 동작
from transformers import AutoProcessor
processor = AutoProcessor.from_pretrained("openai/clip-vit-base-patch32")
동작 순서:
- processor_config.json 존재 여부 확인
- 존재 시 Processor 클래스 로드
- 내부에서 tokenizer + image_processor 각각 로드
5. AutoModel과의 관계
AutoModel은 모델 클래스 선택을 담당하는 factory 계층임.
from transformers import AutoModel
model = AutoModel.from_pretrained("bert-base-uncased")
동작은 다음과 같은 순서로 진행됨:
- config.json 로드
model_type확인- 대응되는 모델 클래스 선택
- 해당 클래스의
from_pretrained호출
Custom 모델도 다음과 같이 AutoModel 에 등록 가능함:
MyModel.register_for_auto_class("AutoModel")
AutoModel은 모델을 구현하지 않음.
Config 기반으로 적절한 PreTrainedModel 클래스를 선택하는 dispatcher임.
6. 실제 Hub 로부터 복원 동작 방식
다음의 순서를 따름:
repo_id
↓
config.json
↓
Config 객체 생성
↓
AutoModel dispatch
↓
PreTrainedModel __init__
↓
model.safetensors 로딩
↓
완전 복원된 모델
- Processor는 별도로 복원됨.
요약
단일 모달(Text)
Raw Text
↓
Tokenizer
↓
input_ids
↓
PreTrainedModel
↓
logits
단일 모달(Image)
Raw Image
↓
ImageProcessor
↓
pixel_values
↓
PreTrainedModel
↓
logits
멀티모달(CLIP)
Raw(Text + Image)
↓
Processor
/ \
Tokenizer ImageProcessor
↓
PreTrainedModel
기억하기
- PreTrainedModel은 Hub 규약을 구현한
nn.Module확장 클래스 - Config는 구조 정의
- Model은 실행 구현
from_pretrained는 단순 weight load가 아님- AutoModel은 config 기반 dispatcher
- Processor는 모델 외부 계층에 해당.