HF-Model
Hugging Face PreTrainedModel의 구조, 기능, 및 활용 튜토리얼 (텍스트/이미지/멀티모달)
- 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임.
"shard"는 본래 깨진 도자기·유리·암석의 파편(조각) 을 뜻하는 명사로서 컴퓨터 과학에선 하나의 큰 데이터셋·데이터베이스·파일을 수평 분할(horizontal partitioning) 한 개별 단위를 가리키는데 사용됨.
대형 언어 모델(LLM)의 가중치(weight) 파일은 수십~수백 GB에 달하여 단일 파일로 저장하기 어렵기 때문에 전체 모델 가중치를 일정 크기(예: 5GB, 10GB) 단위로 분할하여 여러 개의 파일에 나누어 저장하는데, 이 각각의 분할 파일을 shard, 이 방식으로 저장된 가중치를 sharded weight라 함.
참고로, "horizontal partitioning"은 동일한 스키마(열 구조)를 유지하면서 데이터를 행 단위로 분할하는 방식을 의미함.
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 기반으로 올바른 모델 클래스를 선택할 수 있게 함
컴퓨터 과학에서 "dispatch"는 주로 "어떤 호출(call)·요청(request)·이벤트(event) 등을 적절한 처리 주체(함수, 스레드, 큐)로 보내는(라우팅하는) 행위" 를 뜻함.
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 전체를 복원하는 고수준의 복원 메서드 임.
HF의 PreTrainedModel의 from_pretrained()가 state_dict를 어떻게 로딩하는지 다음 절을 참고할 것.
3) state_dict 로딩 전략
state_dict는 PyTorch에서 nn.Module 객체의 parameters와 buffers를 관리하는 collections.OrderedDict 객체임.
- PyTorch 에서 저장과 로드에 사용되는 객체임
- parameters와 buffers의 이름(
str)을 키로 - 해당하는
torch.Tensor를 값으로 가지는 mapping임.
- parameters와 buffers의 이름(
- 참고자료: PyTorch: state_dict()
HF의 PreTrainedModel 객체에서 .from_pretrained() 메서드는
일반 PyTorch의 load_state_dict()와 달리 다음의 개선된 기능을 제공함:
- missing keys 처리
- 체크포인트에는 없으나 현재 모델 구조에는 존재하는 키(예: fine-tuning 시 새로 추가된 레이어의 가중치)를 경고(warning)와 함께 random initialization으로 처리
- 클래스 속성
_keys_to_ignore_on_load_missing을 통해 특정 키를 무시 목록으로 등록할 수 있음.
- unexpected keys 처리
- 체크포인트에는 존재하나 현재 모델 구조에는 없는 키를 경고와 함께 무시(skip)함
- 클래스 속성
_keys_to_ignore_on_load_unexpected을 통해 예상된 불일치를 사전에 등록할 수 있음.
- sharded weight 로딩
- 대형 모델의 가중치를 여러 파일로 분할 저장한 sharded checkpoint(
model.safetensors.index.json또는pytorch_model.bin.index.json)를 - 자동으로 인식하고 순차적으로 로드
- 대형 모델의 가중치를 여러 파일로 분할 저장한 sharded checkpoint(
- safetensors 지원
- 기존
.bin포맷(pickle 기반) 대신 - 보안성과 로딩 속도가 향상된
.safetensors포맷을 우선적으로 탐색하여 로드
- 기존
low_cpu_mem_usage=True옵션 통해 메모리 최적화 로딩.- 가중치 로딩 시 먼저 모델을 meta device (실제 메모리를 점유하지 않는 가상 디바이스)에 생성한 후,
- 실제 가중치 텐서를 레이어 단위로 순차적으로 CPU 메모리에 적재
- 이를 통해 피크 메모리 사용량을 대폭 절감할 수 있음.
- device_map="auto" 옵션 등을 통한
device_map="auto"기반 분산 로딩:- accelerate 라이브러리와 연동하여 사용 가능한 GPU/CPU/Disk 자원을 자동으로 파악
- 이를 통해 레이어 단위로 최적 디바이스에 분산 배치를 수행할 수 있음.
- 추가적으로
device_map에 사용자 정의 딕셔너리를 전달하여 수동 배치도 가능
torch_dtype옵션을 통한dtype자동 캐스팅:from_pretrained(torch_dtype=torch.float16)등을 통해- 로딩 시점에 가중치의 데이터 타입을 자동으로 casting 가능.
- 이를 통해 메모리 사용량 및 추론 속도를 최적화가 가능함.
이는 일반 PyTorch 모델의 load_state_dict(strict=True/False)의 기능을 상회하는 고급 기능들이며, 이를 통해 HF의 PreTrainedMOdel은 사전학습 모델을 보다 효율적으로 사용할 수 있음.
4) post_init()의 역할
Custom 모델 작성 시 생성자(__init__)에서 다음의 hook 호출이 필요.:
self.post_init()
post_init()의 내부 구현은 다음과 같이 세 단계로 구성:
- weight initialization 수행:
self.init_weights()실행:apply(self._init_weights)를 통해 모든 서브모듈을 순회하며 각 레이어 타입에 맞는 초기화를 수행.
self.tie_weights()(가중치 공유)실행- Encoder-Decoder 또는 언어모델 구조에서 입력 embedding 행렬과 출력 lm_head(linear projection)의 가중치를 공유(tying) 함.
- 언어 모델의 입력과 출력은 동일한 vocabulary를 공유하며, 구조적으로 다음과 같이 대칭을 이룸.
- Tying을 적용하여 입력 embedding 행렬(W_e)와 출력 lm_head 행렬(W_o)의 가중치를 공유하도록 처리: $W_e^\top = W_o$
from_pretrained()호출 시에도 tie_weights()가 재실행되므로, 체크포인트 로딩 후에도 tying 상태가 자동 복원
- Encoder-Decoder 또는 언어모델 구조에서 입력 embedding 행렬과 출력 lm_head(linear projection)의 가중치를 공유(tying) 함.
- 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 최소 예제: 커스텀 텍스트 분류 모델
전체 예제는 다음을 참고: 관련 ipynb
import torch
import torch.nn as nn
from transformers import PreTrainedModel, AutoModel, AutoConfig
from transformers.modeling_outputs import SequenceClassifierOutput
class MyTextForSequenceClassification(PreTrainedModel):
config_class = MyTextConfig
def __init__(self, config: MyTextConfig):
super().__init__(config)
# PreTrainedModel.from_pretrained() 는 __init__ 을 항상
# meta device context 안에서 호출함.
# meta context 안에서 중첩 from_pretrained() 호출은 금지됨.
# → torch.empty(1).is_meta 로 복원(meta context)인지/일반 생성인지를 감지해 분기함.
if torch.empty(1).is_meta:
# 복원 경로: 구조만 생성, 가중치는 from_pretrained() 가 자동 복원
# MyTextForSequenceClassification.from_pretrained("./ckpt") 등으로 복원인 경우
backbone_cfg = AutoConfig.from_pretrained(config.backbone_name_or_path)
self.backbone = AutoModel.from_config(backbone_cfg)
else:
# 일반 생성 경로: backbone 사전학습 가중치 포함
# MyTextForSequenceClassification(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가 기대하는 초기화 루틴 정리__init__안에서torch.empty(1).is_meta로 현재 context를 감지해 분기함True(복원 경로):AutoModel.from_config(): 구조만 생성, 가중치는 외부from_pretrained()가 자동 복원False(일반 생성 경로):AutoModel.from_pretrained(): backbone 사전학습 가중치 포함PreTrainedModel.from_pretrained()는__init__을 항상 meta device context 안에서 호출하므로 이 분기 없이는RuntimeError발생함- 확실한 건 transformers 4.51.3부터 우회가 안되어서 무조건
is_meta를 확인하여 처리해야함. low_cpu_mem_usage값과 무관하게 항상meta device context가 적용됨.
- 확실한 건 transformers 4.51.3부터 우회가 안되어서 무조건
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는 모델 외부 계층에 해당.