PEFT documentation
커스텀 모델
커스텀 모델
프롬프트 튜닝과 같은 일부 파인튜닝 기법은 언어 모델에 특화되어 있습니다. 즉, 🤗 PEFT에서는 🤗 Transformers 모델이 사용된다고 가정합니다. 그러나 LoRA와 같은 다른 파인튜닝 기법은 특정 모델 유형으로 제한되지 않습니다.
이 가이드에서는 LoRA를 다층 퍼셉트론, timm 라이브러리의 컴퓨터 비전 모델, 또는 새로운 🤗 Transformers 아키텍처에 적용하는 방법을 살펴봅니다.
다층 퍼셉트론
LoRA로 다층 퍼셉트론을 파인튜닝하고 싶다고 가정해 보겠습니다. 정의는 다음과 같습니다:
from torch import nn
class MLP(nn.Module):
def __init__(self, num_units_hidden=2000):
super().__init__()
self.seq = nn.Sequential(
nn.Linear(20, num_units_hidden),
nn.ReLU(),
nn.Linear(num_units_hidden, num_units_hidden),
nn.ReLU(),
nn.Linear(num_units_hidden, 2),
nn.LogSoftmax(dim=-1),
)
def forward(self, X):
return self.seq(X)이것은 입력층, 은닉층, 출력층을 갖춘 단순한 다층 퍼셉트론입니다.
이 예시에서는 PEFT의 효율성 향상을 부각시키기 위해 은닉 유닛 수를 매우 크게 선택했지만, 이러한 향상은 더 현실적인 예시에서도 일관됩니다.
이 모델에는 LoRA로 튜닝할 수 있는 선형 레이어가 몇 개 있습니다. 일반적인 🤗 Transformers 모델을 다룰 때 PEFT는 LoRA를 적용할 레이어를 알아서 파악하지만, 이 경우에는 사용자가 직접 레이어를 선택해야 합니다. 튜닝할 레이어의 이름을 확인하려면:
print([(n, type(m)) for n, m in MLP().named_modules()])다음과 같이 출력되어야 합니다:
[('', __main__.MLP),
('seq', torch.nn.modules.container.Sequential),
('seq.0', torch.nn.modules.linear.Linear),
('seq.1', torch.nn.modules.activation.ReLU),
('seq.2', torch.nn.modules.linear.Linear),
('seq.3', torch.nn.modules.activation.ReLU),
('seq.4', torch.nn.modules.linear.Linear),
('seq.5', torch.nn.modules.activation.LogSoftmax)]LoRA를 입력층과 은닉층에 적용하고 싶다고 가정해 보겠습니다. 해당 층은 각각 'seq.0'과 'seq.2'입니다. 또한, LoRA 없이 출력층을 업데이트하고 싶다고 가정해 보겠습니다. 해당 층은 'seq.4'입니다. 이에 해당하는 설정은 다음과 같습니다:
from peft import LoraConfig
config = LoraConfig(
target_modules=["seq.0", "seq.2"],
modules_to_save=["seq.4"],
)이제 PEFT 모델을 생성하고 학습된 매개변수의 비율을 확인할 수 있습니다:
from peft import get_peft_model
model = MLP()
peft_model = get_peft_model(model, config)
peft_model.print_healthcheck()마지막으로, 원하는 어떤 학습 프레임워크를 사용하거나 자체 fit 루프를 작성하여 peft_model을 학습할 수 있습니다.
완전한 예제는 이 노트북을 확인하세요.
timm 모델
timm 라이브러리에는 사전 학습된 컴퓨터 비전 모델이 다수 포함되어 있습니다. 이러한 모델은 PEFT를 사용하여 파인튜닝할 수도 있습니다. 실제 사용 방법을 살펴보겠습니다.
시작하기 전에 Python 환경에 timm이 설치되어 있는지 확인하세요:
python -m pip install -U timm
다음으로 이미지 분류 작업을 위한 timm 모델을 로드합니다:
import timm
num_classes = ...
model_id = "timm/poolformer_m36.sail_in1k"
model = timm.create_model(model_id, pretrained=True, num_classes=num_classes)다시 한 번, LoRA를 적용할 레이어에 대한 결정을 내려야 합니다. LoRA는 2D conv 레이어를 지원하며, 이 모델의 주요 구성 요소이기도 하므로 2D conv 레이어에 LoRA를 적용해야 합니다. 해당 레이어의 이름을 확인하기 위해 모든 레이어 이름을 살펴보겠습니다:
print([(n, type(m)) for n, m in model.named_modules()])이 명령을 실행하면 매우 긴 목록이 출력되지만, 여기서는 처음 몇 개만 보여 드리겠습니다:
[('', timm.models.metaformer.MetaFormer),
('stem', timm.models.metaformer.Stem),
('stem.conv', torch.nn.modules.conv.Conv2d),
('stem.norm', torch.nn.modules.linear.Identity),
('stages', torch.nn.modules.container.Sequential),
('stages.0', timm.models.metaformer.MetaFormerStage),
('stages.0.downsample', torch.nn.modules.linear.Identity),
('stages.0.blocks', torch.nn.modules.container.Sequential),
('stages.0.blocks.0', timm.models.metaformer.MetaFormerBlock),
('stages.0.blocks.0.norm1', timm.layers.norm.GroupNorm1),
('stages.0.blocks.0.token_mixer', timm.models.metaformer.Pooling),
('stages.0.blocks.0.token_mixer.pool', torch.nn.modules.pooling.AvgPool2d),
('stages.0.blocks.0.drop_path1', torch.nn.modules.linear.Identity),
('stages.0.blocks.0.layer_scale1', timm.models.metaformer.Scale),
('stages.0.blocks.0.res_scale1', torch.nn.modules.linear.Identity),
('stages.0.blocks.0.norm2', timm.layers.norm.GroupNorm1),
('stages.0.blocks.0.mlp', timm.layers.mlp.Mlp),
('stages.0.blocks.0.mlp.fc1', torch.nn.modules.conv.Conv2d),
('stages.0.blocks.0.mlp.act', torch.nn.modules.activation.GELU),
('stages.0.blocks.0.mlp.drop1', torch.nn.modules.dropout.Dropout),
('stages.0.blocks.0.mlp.norm', torch.nn.modules.linear.Identity),
('stages.0.blocks.0.mlp.fc2', torch.nn.modules.conv.Conv2d),
('stages.0.blocks.0.mlp.drop2', torch.nn.modules.dropout.Dropout),
('stages.0.blocks.0.drop_path2', torch.nn.modules.linear.Identity),
('stages.0.blocks.0.layer_scale2', timm.models.metaformer.Scale),
('stages.0.blocks.0.res_scale2', torch.nn.modules.linear.Identity),
('stages.0.blocks.1', timm.models.metaformer.MetaFormerBlock),
('stages.0.blocks.1.norm1', timm.layers.norm.GroupNorm1),
('stages.0.blocks.1.token_mixer', timm.models.metaformer.Pooling),
('stages.0.blocks.1.token_mixer.pool', torch.nn.modules.pooling.AvgPool2d),
...
('head.global_pool.flatten', torch.nn.modules.linear.Identity),
('head.norm', timm.layers.norm.LayerNorm2d),
('head.flatten', torch.nn.modules.flatten.Flatten),
('head.drop', torch.nn.modules.linear.Identity),
('head.fc', torch.nn.modules.linear.Linear)]
]자세히 살펴보면, 2D conv 레이어의 이름이 "stages.0.blocks.0.mlp.fc1" 또는 "stages.0.blocks.0.mlp.fc2"와 같은 형태임을 알 수 있습니다. 이러한 레이어 이름을 어떻게 구체적으로 일치시킬 수 있을까요? 레이어 이름을 일치시키기 위해 정규 표현식을 작성할 수 있습니다. 이 경우, r".*\.mlp\.fc\d" 정규 표현식이 역할을 수행할 것입니다.
또한 첫 번째 예시와 마찬가지로, 이 경우 분류 헤드인 출력 레이어도 업데이트되도록 해야 합니다. 위에서 출력된 목록의 끝을 보면 'head.fc'라는 이름으로 지정되어 있는 것을 확인할 수 있습니다. 이를 염두에 두고, 우리의 LoRA 설정은 다음과 같습니다:
config = LoraConfig(target_modules=r".*\.mlp\.fc\d", modules_to_save=["head.fc"])그 다음에는 기본 모델과 설정을 get_peft_model에 전달하여 PEFT 모델을 생성하기만 하면 됩니다:
peft_model = get_peft_model(model, config) peft_model.print_healthcheck()
이것은 전체 파라미터의 2% 미만만 학습하면 된다는 것을 보여주며, 이는 상당한 효율성 향상입니다.
완전한 예제는 이 노트북을 확인하세요.
새로운 transformers 아키텍처
새로운 인기 있는 transformers 아키텍처가 출시되면 PEFT에 빠르게 추가하도록 최선을 다합니다. 기본 지원되지 않는 transformers 모델을 발견하더라도 걱정하지 마세요. 설정이 올바르게 구성되면 대부분 여전히 작동합니다. 구체적으로, 적응해야 하는 레이어를 식별하고 LoraConfig와 같은 해당 config 클래스를 초기화할 때 올바르게 설정해야 합니다. 이를 돕기 위한 몇 가지 팁을 아래에 제공합니다.
첫 단계로, 영감을 얻기 위해 기존 모델을 확인하는 것이 좋습니다. PEFT 저장소의 constants.py 파일에서 이를 찾을 수 있습니다. 동일한 이름을 사용하는 유사한 아키텍처를 자주 발견하게 됩니다. 예를 들어, 새로운 모델 아키텍처가 “mistral” 모델의 변형이고 LoRA를 적용하려는 경우, TRANSFORMERS_MODELS_TO_LORA_TARGET_MODULES_MAPPING의 “mistral” 항목에 ["q_proj", "v_proj"]가 포함되어 있음을 확인할 수 있습니다. 이는 “mistral” 모델의 경우 LoRA의 target_modules가 ["q_proj", "v_proj"]여야 함을 의미합니다:
from peft import LoraConfig, get_peft_model
my_mistral_model = ...
config = LoraConfig(
target_modules=["q_proj", "v_proj"],
..., # other LoRA arguments
)
peft_model = get_peft_model(my_mistral_model, config)그렇게 해결되지 않으면, named_modules 메서드를 사용하여 모델 아키텍처의 기존 모듈을 확인하고 attention 레이어, 특히 key, query, value 레이어를 식별해 보세요. 이러한 레이어는 c_attn, query, q_proj와 같은 이름을 가지는 경우가 많습니다. key 레이어는 항상 적응되지 않으며, 이상적으로는 이를 포함했을 때 성능이 향상되는지 확인해야 합니다.
또한, 선형 레이어는 적응 대상이 되는 경우가 많습니다(예: QLoRA 논문에서는 저자들이 이들도 적응하도록 제안합니다). 그 이름에는 fc 또는 dense 문자열이 포함된 경우가 많습니다.
PEFT에 새 모델을 추가하려면 constants.py에 항목을 생성하고 repository에 pull request를 열어 주세요. README도 함께 업데이트하는 것을 잊지 마세요.
파라미터 및 레이어 확인
PEFT 방법을 모델에 올바르게 적용했는지 확인하는 방법은 여러 가지가 있습니다.
- print_healthcheck()을 실행하여 학습 가능한 매개변수 비율과 어댑터 상태를 확인합니다. 매개변수 비율이 예상보다 낮거나 높다면, 모델을 출력하여 모델의
repr을 확인합니다. 이렇게 하면 모델 내 모든 레이어 유형의 이름이 표시됩니다. 의도된 대상 레이어만 어댑터 레이어로 대체되었는지 확인합니다. 예를 들어 LoRA가nn.Linear레이어에 적용된 경우,lora.Linear레이어만 사용되고 있는지 확인해야 합니다.
peft_model.print_healthcheck()
- 적응된 레이어를 확인하는 또 다른 방법은
targeted_module_names속성을 사용하여 적응된 각 모듈의 이름을 나열하는 것입니다.
print(peft_model.targeted_module_names)지원되지 않는 모듈 유형
LoRA와 같은 방법은 PEFT가 대상 모듈을 지원하는 경우에만 작동합니다. 예를 들어, nn.Linear 및 nn.Conv2d 레이어에는 LoRA를 적용할 수 있지만, nn.LSTM에는 적용할 수 없습니다. PEFT를 적용하려는 레이어 클래스가 지원되지 않는 경우 다음을 수행할 수 있습니다:
- LoRA에서 커스텀 모듈을 동적으로 디스패치하기 위해 커스텀 매핑을 정의합니다
- issue를 열고 해당 모듈 유형에 대한 수요가 충분히 높을 경우 유지보수자가 해당 기능을 구현하거나 직접 구현하는 방법을 안내해 줄 수 있도록 기능 요청을 하세요
LoRA에서 커스텀 모듈의 동적 디스패치에 대한 실험적 지원
이 기능은 실험적이며, 커뮤니티의 반응에 따라 변경될 수 있습니다. 이에 대한 수요가 상당할 경우 공개적이고 안정적인 API를 도입할 예정입니다.
PEFT는 LoRA의 커스텀 모듈 유형을 위한 실험적 API를 지원합니다. LSTMs에 대한 LoRA 구현이 있다고 가정해 보겠습니다. 이론적으로 PEFT와 호환되더라도, 일반적으로 PEFT에 이를 사용하도록 지시할 수 없습니다. 그러나 커스텀 레이어의 동적 디스패치를 사용하면 가능합니다.
현재 실험적 API는 다음과 같습니다:
class MyLoraLSTMLayer:
...
base_model = ... # load the base model that uses LSTMs
# add the LSTM layer names to target_modules
config = LoraConfig(..., target_modules=["lstm"])
# define a mapping from base layer type to LoRA layer type
custom_module_mapping = {nn.LSTM: MyLoraLSTMLayer}
# register the new mapping
config._register_custom_module(custom_module_mapping)
# after registration, create the PEFT model
peft_model = get_peft_model(base_model, config)
# do trainingget_peft_model()을 호출하면 PEFT가 대상 모듈 유형을 인식하지 못해 경고가 표시됩니다. 이 경우 해당 경고를 무시할 수 있습니다.
사용자가 커스텀 매핑을 제공하면, PEFT는 먼저 베이스 모델의 레이어를 커스텀 매핑과 비교하여 일치하는 항목이 있으면 커스텀 LoRA 레이어 유형으로 분기합니다. 일치하는 항목이 없으면 PEFT는 내장된 LoRA 레이어 유형을 확인합니다.
따라서 이 기능은 기존 디스패치 로직을 오버라이드하는 데에도 사용할 수 있습니다. 예를 들어, PEFT에서 제공하는 대신 nn.Linear에 자체 LoRA 레이어를 사용하려는 경우입니다.
사용자 정의 LoRA 모듈을 생성할 때는 기존 LoRA 모듈과 동일한 규칙을 따르세요. 고려해야 할 몇 가지 중요한 제약 사항은 다음과 같습니다:
- 커스텀 모듈은
nn.Module과peft.tuners.lora.layer.LoraLayer를 상속해야 합니다. - 커스텀 모듈의
__init__메서드에는base_layer와adapter_name라는 위치 인자가 있어야 합니다. 이 뒤에**kwargs가 추가되며, 이를 자유롭게 사용하거나 무시할 수 있습니다. - 학습 가능한 파라미터는
nn.ModuleDict또는nn.ParameterDict에 저장해야 하며, 여기서 키는 특정 어댑터의 이름에 대응합니다(모델에는 동시에 여러 개의 어댑터가 있을 수 있음을 기억하세요). - 이러한 학습 가능한 파라미터 속성의 이름은
"lora_"로 시작해야 하며, 예를 들어self.lora_new_param = ...와 같습니다. - 일부 메서드는 선택 사항입니다. 예를 들어, 가중치 병합을 지원하려면
merge와unmerge만 구현하면 됩니다.
현재 모델 저장 시 커스텀 모듈에 대한 정보가 유지되지 않습니다. 모델을 로드할 때 커스텀 모듈을 다시 등록해야 합니다.
# saving works as always and includes the parameters of the custom modules
peft_model.save_pretrained(<model-path>)
# loading the model later:
base_model = ...
# load the LoRA config that you saved earlier
config = LoraConfig.from_pretrained(<model-path>)
# register the custom module again, the same way as the first time
custom_module_mapping = {nn.LSTM: MyLoraLSTMLayer}
config._register_custom_module(custom_module_mapping)
# pass the config instance to from_pretrained:
peft_model = PeftModel.from_pretrained(model, tmp_path / "lora-custom-module", config=config)이 기능을 사용하면서 유용하다고 느끼거나 문제를 겪는 경우, GitHub에서 이슈나 토론을 생성하여 알려 주세요. 이를 통해 해당 기능에 대한 수요를 파악하고, 수요가 충분히 높을 경우 공개 API를 추가할 수 있습니다.
Update on GitHub