https://huggingface.co/docs/transformers/v4.57.1/quicktour
Quickstart
huggingface.co
from transformers import AutoModelForCausalLM
model = AutoModelForCausalLM.from_pretrained("meta-llama/Llama-2-7b-hf", torch_dtype="auto", device_map="auto")
여기선 safetensors 파일 형식으로 저장된 가중치가 있다면 로드한다.
기존 PyTorch는 보안에 취약한 pickle 유틸맅티로 직렬화 되어있었는데 Safetensor파일은 더 안전하고 속도가 빠름
configuration.py - hidden layer 수, 사전 크기, activation function 등 저장
modeling.py - 각 레이어의 정의와 레이어 안에서 일어나는 수학적 연산을 정의
모델의 타입
- AutoModel - hidden state를 출력
- CausalLM - Head가 붙어 특정 작업을 수행
from transformers import AutoModelForCausalLM, MistralForCausalLM
# AutoClass 또는 모델별 클래스(model-specific class) 를 이용해 로드
model = AutoModelForCausalLM.from_pretrained("mistralai/Mistral-7B-v0.1", torch_dtype="auto", device_map="auto")
model = MistralForCausalLM.from_pretrained("mistralai/Mistral-7B-v0.1", torch_dtype="auto", device_map="auto")
Automodel - 모델 구조와 특성을 자동으로 파악해줌 및 다양한 작업에 다른 헤드를 달아서 사용할 수 있음
from transformers import (
AutoModelForCausalLM, # 언어 생성 (텍스트 생성) 용도
AutoModelForSequenceClassification, # 분류하기 용도
AutoModelForQuestionAnswering # 질의응답 용도
)
# 동일한 체크포인트(meta-llama/Llama-2-7b-hf)지만 작업별로 불러오는 클래스가 다름
model_gen = AutoModelForCausalLM.from_pretrained("meta-llama/Llama-2-7b-hf")
model_cls = AutoModelForSequenceClassification.from_pretrained("meta-llama/Llama-2-7b-hf")
model_qa = AutoModelForQuestionAnswering.from_pretrained("meta-llama/Llama-2-7b-hf")
모델을 불러오는 과정은 아래와 같다
- random weight로 model 생성
- pre-trained weight 로드
- weight를 model에 적용
모델 저장은 하나에 하는 것이 아니라
model-00001-of-00003.safetensors, model-00002-of-00003.safetensors
이런 식으로 각각 파라미터를 나눠서 저장함
그건 save_pretrained에서 알아서 해주고, 읽을 때도 알아서 해줌
from transformers import AutoModel
import tempfile
import os
model = AutoModel.from_pretrained("biomistral/biomistral-7b")
with tempfile.TemporaryDirectory() as tmp_dir:
model.save_pretrained(tmp_dir, max_shard_size="5GB")
print(sorted(os.listdir(tmp_dir)))
모델 불러오기
with tempfile.TemporaryDirectory() as tmp_dir:
model.save_pretrained(tmp_dir)
new_model = AutoModel.from_pretrained(tmp_dir)
모델을 새로 지정하는 것이아니라 기존 모델에 파라미터 덮어 씌우기도 가능
from transformers.modeling_utils import load_sharded_checkpoint
with tempfile.TemporaryDirectory() as tmp_dir:
model.save_pretrained(tmp_dir, max_shard_size="5GB")
load_sharded_checkpoint(model, tmp_dir)
metadata는 모델의 전체 크기를 제공하며, 각 샤드와 모델의 위치를 지정해줌
device_map은 자동으로 모델의 가중치를 사용 가능한 모든 디바이스에 분산하며 가장 빠른 디바이스(GPU)부터 느린 디바이스(CPU, 하드디스크)로 할당 -> 메모리 사용량과 로딩 시간이 줄어든다.
hf_device_map 레이어나 모듈이 어디에 올라가있는지 파악
from transformers import AutoModelForCausalLM
model = AutoModelForCausalLM.from_pretrained(
"meta-llama/Llama-2-7b-hf",
device_map="auto"
)
# 모델이 실제로 각 디바이스에 어떻게 분포되어 있는지 확인
print(model.hf_device_map)
## 출력
{
"model.layers.1": 0,
"model.layers.14": 1,
"model.layers.31": "cpu",
"lm_head": "disk"
}
torch_dtype : Pytorch는 기본적으로 float32형이기 때문에 다른 데이터 타입으로 바꾸려면 다시 로드를 해야 하기 때문에 추가 메모리가 필요하다 -> 데이터 타입을 명시적으로 설정하여 한번에 하기
trust_remote_code - 허깅페이스에서 기본적으로 악성코드 스캔이 있지만 여전히 존재할 수 있기에 커스텀 모델은 한번 더 물어본다.
물어보는 것이 귀찮으면 True로 두기
특정 버전을 불러올 수도 있다.
commit_hash = "ed94a7c6247d8aedce4647f00f20de6875b5b292"
model = AutoModelForImageClassification.from_pretrained(
"sgugger/custom-resnet50d", trust_remote_code=True, revision=commit_hash
)
모델 공유하기
모델을 만들기 위해선 configuration을 통해 모델을 초기화하기 위한 구성이 필요하다.
from transformers import PretrainedConfig
from typing import List
# 이미지 인식 모델 ResNet을 위한 설정(Config) 클래스 선언
class ResnetConfig(PretrainedConfig):
# Hugging Face 모델 타입 명시 (모델 저장/호환시 사용)
model_type = "resnet"
def __init__(
self,
block_type="bottleneck", # 블록 종류: basic(간단) or bottleneck(깊은 네트워크에 적합)
layers: list[int] = [3, 4, 6, 3], # 각 stage별 block 개수 (예: ResNet-50 구조)
num_classes: int = 1000, # 분류 예측 클래스 개수 (이미지넷 기준 1000)
input_channels: int = 3, # 입력 채널 수 (RGB 이미지면 3)
cardinality: int = 1, # 그룹 합성곱에 쓰이는 그룹 개수 (ResNeXt 용)
base_width: int = 64, # 블록 내부 합성곱의 기본 channel width
stem_width: int = 64, # 입력 레이어(stem) channel 크기
stem_type: str = "", # stem 레이어 구조 선택 ("", "deep", "deep-tiered")
avg_down: bool = False, # 다운샘플링(스트라이드)시 avgpool 활용 여부 (성능 옵션)
**kwargs, # 추가 파라미터 (상위 PretrainedConfig에서 관리)
):
# block_type validation: 올바른 값 아니면 에러 발생
if block_type not in ["basic", "bottleneck"]:
raise ValueError(f"`block_type` must be 'basic' or bottleneck', got {block_type}.")
# stem_type validation
if stem_type not in ["", "deep", "deep-tiered"]:
raise ValueError(f"`stem_type` must be '', 'deep' or 'deep-tiered', got {stem_type}.")
# 각 파라미터를 객체 속성으로 저장
self.block_type = block_type
self.layers = layers
self.num_classes = num_classes
self.input_channels = input_channels
self.cardinality = cardinality
self.base_width = base_width
self.stem_width = stem_width
self.stem_type = stem_type
self.avg_down = avg_down
# 나머지 인자들은 부모 클래스(PretrainedConfig)에 전달
super().__init__(**kwargs)
PretrainedConfig를 상속해야 하며 __init__의 모든 kwargs를 허용하며 상위 클래스 __init__에 전달되어야 한다.
# 모델 만들고 저장하기
resnet50d_config = ResnetConfig(block_type="bottleneck", stem_width=32, stem_type="deep", avg_down=True)
resnet50d_config.save_pretrained("custom-resnet")
# 모델 로드하기
resnet50d_config = ResnetConfig.from_pretrained("custom-resnet")
이제 모델 구성이 있으니 작성할 수 있다.
하나는 분류, 하나는 hidden features 추출이다.
from transformers import PreTrainedModel # Hugging Face 모델 표준 클래스
from timm.models.resnet import BasicBlock, Bottleneck, ResNet # timm 라이브러리의 ResNet 관련 클래스
from .configuration_resnet import ResnetConfig # Custom Config 클래스 (아까 작성한 클래스)
# 블록 종류에 따라 대응되는 클래스 매핑 (ResNet의 기본/병목 블록)
BLOCK_MAPPING = {
"basic": BasicBlock,
"bottleneck": Bottleneck
}
# Hugging Face 프레임워크와 호환되는 Custom ResNet 모델 클래스
class ResnetModel(PreTrainedModel):
# 사용할 config 클래스 명시 (후에 모델 저장/로드시 자동 연결됨)
config_class = ResnetConfig
def __init__(self, config):
# 부모 클래스 초기화 (Hugging Face 표준 로직 사용)
super().__init__(config)
# 설정(config)에 따라 사용할 블록 클래스 결정 ("basic" 또는 "bottleneck")
block_layer = BLOCK_MAPPING[config.block_type]
# 실제 timm의 ResNet 모델 생성
self.model = ResNet(
block_layer, # 사용할 블록 종류
config.layers, # 각 스테이지별 블록 개수 (e.g. [3,4,6,3])
num_classes=config.num_classes, # 분류 클래스 수
in_chans=config.input_channels, # 입력 채널 수 (보통 3, RGB)
cardinality=config.cardinality, # 그룹 합성곱 그룹 수 (ResNeXt에서 사용)
base_width=config.base_width, # 블록 내부 기본 채널 수
stem_width=config.stem_width, # 입력(stem) 레이어 채널 수
stem_type=config.stem_type, # stem 구조 타입 ("", "deep" 등)
avg_down=config.avg_down, # 다운샘플링시 avgpool 사용할지 여부
)
def forward(self, tensor):
# 입력 텐서에 대한 모델의 forward 과정 정의
# timm ResNet의 features 추출 함수 사용 (forward_features)
return self.model.forward_features(tensor)
이제 분류 모델에는 forward 메소드만 마지막에 변경하면 된다.
import torch
# 이미지 분류 전용 Custom Hugging Face 모델 클래스
class ResnetModelForImageClassification(PreTrainedModel):
# 사용할 Config 클래스 명시 (후에 모델 저장/로드/배포시 자동 연결됨)
config_class = ResnetConfig
def __init__(self, config):
# 부모 클래스 초기화 (Hugging Face 프레임워크 내부 표준 로직)
super().__init__(config)
# config에 등록된 block_type에 따라 BasicBlock/Bottleneck 선택
block_layer = BLOCK_MAPPING[config.block_type]
# 실제 timm의 ResNet 인스턴스 생성 (입력 config 기반 구조 설정)
self.model = ResNet(
block_layer, # 사용할 블록 유형
config.layers, # 각 stage별 블록 개수 (리스트)
num_classes=config.num_classes, # 분류 예측 클래스 수 (예: 이미지넷 1000)
in_chans=config.input_channels, # 입력 채널 수 (RGB=3)
cardinality=config.cardinality, # 그룹 합성곱 그룹 개수 (ResNeXt 용)
base_width=config.base_width, # 블록 내부 채널수 기본값
stem_width=config.stem_width, # 입력 레이어(stem) 채널 크기
stem_type=config.stem_type, # stem 구조 형태 ("", "deep", "deep-tiered")
avg_down=config.avg_down, # 다운샘플링시 avgpool 활용 여부
)
def forward(self, tensor, labels=None):
# 입력 이미지를 모델에 통과시켜 예측 logits(클래스별 스코어) 획득
logits = self.model(tensor)
# labels가 들어온 경우 (학습 or eval시 정답 레이블이 있음)
if labels is not None:
# cross_entropy로 loss 계산 (예측값=logits, 정답=labels)
loss = torch.nn.functional.cross_entropy(logits, labels)
# 평소 Hugging Face 모델 출력 형태에 맞춰 딕셔너리로 반환 (loss 포함)
return {"loss": loss, "logits": logits}
# labels가 없으면 inference 용 (logits만 반환)
return {"logits": logits}
지정해둔 ResNet에서 연산도 다 진행됨
# 모델 생성하기
resnet50d = ResnetModelForImageClassification(resnet50d_config)
# 모델 가중치 불러오기
# 동일한 모델 형태이기 때문에 가중치 로드 가능
import timm
pretrained_model = timm.create_model("resnet50d", pretrained=True)
resnet50d.model.load_state_dict(pretrained_model.state_dict())
Hub로 코드 업로드 하기 위해선 modeling과 config python 코드가 모두 같은 경로안에 있어야 함
.
└── resnet_model
├── __init__.py
├── configuration_resnet.py
└── modeling_resnet.py
# 모델과 구성 import 하기
from resnet_model.configuration_resnet import ResnetConfig
from resnet_model.modeling_resnet import ResnetModel, ResnetModelForImageClassification
# 저장하기
ResnetConfig.register_for_auto_class()
ResnetModel.register_for_auto_class("AutoModel")
ResnetModelForImageClassification.register_for_auto_class("AutoModelForImageClassification")
모델일 경우에는 AutoModel 지정해줘야 함
구성과 모델 작성
resnet50d_config = ResnetConfig(block_type="bottleneck", stem_width=32, stem_type="deep", avg_down=True)
resnet50d = ResnetModelForImageClassification(resnet50d_config)
pretrained_model = timm.create_model("resnet50d", pretrained=True)
resnet50d.model.load_state_dict(pretrained_model.state_dict())
resnet50d.push_to_hub("custom-resnet50d")
이런 형식을 통해 모델 저장 가능함
이제 이렇게 불러올 수 있음
from transformers import AutoModelForImageClassification
model = AutoModelForImageClassification.from_pretrained("sgugger/custom-resnet50d", trust_remote_code=True)
구성 요소 변경
모델을 새로 작성하는 대신 구성 요소를 수정하여 모델 맞춤 설정하는 방버이 있음 => 이를 통해 특정 사용 사례에 맞게 모델 조정 가능함
clear import cache를 통해 캐시된 트랜스포머 모듈을 제거하여 Python이 환경을 재시작하지 않아도 수정된 코드를 다시 가져올 수 있음
from transformers import AutoModel
from transformers.utils.import_utils import clear_import_cache
model = AutoModel.from_pretrained("bert-base-uncased")
# 모델 코드 수정
# 캐시를 지워 수정된 코드를 다시 가져오기
clear_import_cache()
# 업데이트된 코드를 사용하기 위해 다시 가져오기
model = AutoModel.from_pretrained("bert-base-uncased")
원래는 qkv를 하나의 큰 Linear로 처리하지만 LoRA를 위해 3개의 각각 q, k, v로 나눠준다.
import torch
import torch.nn as nn
from transformers.models.sam.modeling_sam import SamVisionAttention
class SamVisionAttentionSplit(SamVisionAttention, nn.Module):
def __init__(self, config, window_size):
super().__init__(config, window_size)
# 결합된 qkv 제거
del self.qkv
# q, k, v 개별 프로젝션 생성
self.q = nn.Linear(config.hidden_size, config.hidden_size, bias=config.qkv_bias)
self.k = nn.Linear(config.hidden_size, config.hidden_size, bias=config.qkv_bias)
self.v = nn.Linear(config.hidden_size, config.hidden_size, bias=config.qkv_bias)
self._register_load_state_dict_pre_hook(self.split_q_k_v_load_hook)
개별로 만들어준다!
정의만 되어있는 상태로 아직 사전학습 된 가중치는 할당되지 않았음
def split_q_k_v_load_hook(self, state_dict, prefix, *args):
keys_to_delete = []
for key in list(state_dict.keys()):
if "qkv." in key:
# 결합된 프로젝션에서 q, k, v 분리
q, k, v = state_dict[key].chunk(3, dim=0)
# 개별 q, k, v 프로젝션으로 대체
state_dict[key.replace("qkv.", "q.")] = q
state_dict[key.replace("qkv.", "k.")] = k
state_dict[key.replace("qkv.", "v.")] = v
# 기존 qkv 키를 삭제 대상으로 표시
keys_to_delete.append(key)
# 기존 qkv 키 제거
for key in keys_to_delete:
del state_dict[key]
기존에 합쳐진 가중치를 각각 나눠서 전달해주는 역할을 한다.
def forward(self, hidden_states: torch.Tensor, output_attentions=False) -> torch.Tensor:
batch_size, height, width, _ = hidden_states.shape
qkv_shapes = (batch_size * self.num_attention_heads, height * width, -1)
query = self.q(hidden_states).reshape((batch_size, height * width,self.num_attention_heads, -1)).permute(0,2,1,3).reshape(qkv_shapes)
key = self.k(hidden_states).reshape((batch_size, height * width,self.num_attention_heads, -1)).permute(0,2,1,3).reshape(qkv_shapes)
value = self.v(hidden_states).reshape((batch_size, height * width,self.num_attention_heads, -1)).permute(0,2,1,3).reshape(qkv_shapes)
attn_weights = (query * self.scale) @ key.transpose(-2, -1)
attn_weights = torch.nn.functional.softmax(attn_weights, dtype=torch.float32, dim=-1).to(query.dtype)
attn_probs = nn.functional.dropout(attn_weights, p=self.dropout, training=self.training)
attn_output = (attn_probs @ value).reshape(batch_size, self.num_attention_heads, height, width, -1)
attn_output = attn_output.permute(0, 2, 3, 1, 4).reshape(batch_size, height, width, -1)
attn_output = self.proj(attn_output)
if output_attentions:
outputs = (attn_output, attn_weights)
else:
outputs = (attn_output, None)
return outputs
Attention 연산을 통해 forward하는 함수를 정의해준다.
from transformers import SamModel
# 사전 훈련된 SAM 모델 가져오기
model = SamModel.from_pretrained("facebook/sam-vit-base")
# 비전-인코더 모듈에서 어텐션 클래스 교체
for layer in model.vision_encoder.layers:
if hasattr(layer, "attn"):
layer.attn = SamVisionAttentionSplit(model.config.vision_config, model.config.vision_config.window_size)
이제 이렇게 변경해줄 수 있다.
이제 LoRA를 단다.
from peft import LoraConfig, get_peft_model
config = LoraConfig(
r=16, # 추가되는 저랭크 행렬의 차원(랭크)
lora_alpha=32, # Scaling factor; 보통 r과 비슷하게 설정
target_modules=["q", "v"], # LoRA를 적용할 모듈 이름(q, v에 각각 적용)
lora_dropout=0.1, # 학습시 드롭아웃 적용해 일반화
task_type="FEATURE_EXTRACTION"
)
이제 LoRA config를 준비해뒀으니 모델에 달아보자
model = get_peft_model(model, config)
model.print_trainable_parameters()
"trainable params: 589,824 || all params: 94,274,096 || trainable%: 0.6256"
이제 이러고 학습하면 된다!
'인공지능 > 자연어 처리' 카테고리의 다른 글
| 허깅페이스 4 기초 - Trainer (2) | 2025.11.16 |
|---|---|
| 허깅페이스 3 기초 - Audio Feature Extractors (0) | 2025.11.15 |
| Embedding 모델 학습하기 - Sentence Transformer Trainer (0) | 2025.09.07 |
| LLM Pruning to Encoder - Large Language Models Are Overparameterized Text Encoders (2) | 2025.07.17 |
| Generation + Embedding 세미나 준비 (0) | 2025.06.12 |