PEFT documentation
PEPT 체크포인트 형식
PEPT 체크포인트 형식
이 문서는 PEFT의 체크포인트 파일 구조와 PEFT 형식과 다른 형식 간의 변환 방법을 설명합니다.
PEPT 파일
PEFT(파라미터 효율적 미세 조정) 방법은 모델의 모든 파라미터가 아닌 일부만 업데이트합니다. 이는 체크포인트 파일이 원래 모델 파일보다 일반적으로 훨씬 작아 저장 및 공유가 용이하기 때문에 장점입니다. 그러나 이는 PEFT 모델을 로드하려면 원래 모델도 함께 사용 가능해야 함을 의미합니다.
PEFT 모델에서 save_pretrained()를 호출하면 아래에 설명된 세 개의 파일이 저장됩니다:
adapter_model.safetensors또는adapter_model.bin
기본적으로 모델은 safetensors 형식으로 저장됩니다. 이 형식은 bin 형식의 안전한 대안으로, bin 형식은 내부적으로 pickle 유틸리티를 사용하여 보안 취약점에 노출될 수 있는 것으로 알려져 있습니다. 두 형식 모두 동일한 state_dict를 저장하므로 서로 호환됩니다.
state_dict에는 베이스 모델이 아닌 어댑터 모듈의 파라미터만 포함되어 있습니다. 크기의 차이를 보여주기 위해, 일반적인 BERT 모델은 약 420MB의 디스크 공간이 필요한 반면, 이 BERT 모델 위에 적용된 IA³ 어댑터는 약 260KB만 필요합니다.
adapter_config.json
adapter_config.json 파일에는 모델을 로드하는 데 필요한 어댑터 모듈의 구성이 포함되어 있습니다. 아래는 BERT 모델에 표준 설정이 적용된 IA³ 어댑터의 adapter_config.json 예시입니다:
{
"auto_mapping": {
"base_model_class": "BertModel",
"parent_library": "transformers.models.bert.modeling_bert"
},
"base_model_name_or_path": "bert-base-uncased",
"fan_in_fan_out": false,
"feedforward_modules": [
"output.dense"
],
"inference_mode": true,
"init_ia3_weights": true,
"modules_to_save": null,
"peft_type": "IA3",
"revision": null,
"target_modules": [
"key",
"value",
"output.dense"
],
"task_type": null
}구성 파일에는 다음이 포함되어 있습니다:
- 저장된 어댑터 모듈 유형,
"peft_type": "IA3" "base_model_name_or_path": "bert-base-uncased"와 같은 베이스 모델 정보- 모델의 리비전(있는 경우),
"revision": null
기본 모델이 사전 학습된 Transformers 모델이 아닌 경우, 마지막 두 항목은 null입니다. 그 외 설정은 모두 모델을 미세 조정하는 데 사용된 특정 IA³ 어댑터와 관련이 있습니다.
README.md
생성된 README.md는 PEFT 모델의 모델 카드이며, 몇 가지 항목이 미리 입력되어 있습니다. 이는 모델을 다른 사람과 공유하기 쉽게 하고 모델에 대한 기본 정보를 제공하기 위한 것입니다. 이 파일은 모델을 로드하는 데 필요하지 않습니다.
PEFT 형식으로 변환
다른 형식에서 PEFT 형식으로 변환할 때 adapter_model.safetensors(또는 adapter_model.bin) 파일과 adapter_config.json 파일이 모두 필요합니다.
adapter_model
모델 가중치의 경우, PEFT가 파일을 로드하려면 매개변수 이름에서 값으로의 올바른 매핑을 사용하는 것이 중요합니다. PEFT 어댑터에 대해 일반적으로 합의된 형식이 없으므로, 이 매핑을 올바르게 설정하는 것은 구현 세부 사항을 확인하는 과정입니다.
다행히도 일반적인 기본 사례에서 이 매핑을 파악하는 것은 그리 복잡하지 않습니다. 구체적인 예시로 LoraLayer를 살펴보겠습니다:
# showing only part of the code
class LoraLayer(BaseTunerLayer):
# All names of layers that may contain (trainable) adapter weights
adapter_layer_names = ("lora_A", "lora_B", "lora_embedding_A", "lora_embedding_B")
# All names of other parameters that may contain adapter-related parameters
other_param_names = ("r", "lora_alpha", "scaling", "lora_dropout")
def __init__(self, base_layer: nn.Module, **kwargs) -> None:
self.base_layer = base_layer
self.r = {}
self.lora_alpha = {}
self.scaling = {}
self.lora_dropout = nn.ModuleDict({})
self.lora_A = nn.ModuleDict({})
self.lora_B = nn.ModuleDict({})
# For Embedding layer
self.lora_embedding_A = nn.ParameterDict({})
self.lora_embedding_B = nn.ParameterDict({})
# Mark the weight as unmerged
self._disable_adapters = False
self.merged_adapters = []
self.use_dora: dict[str, bool] = {}
self.lora_magnitude_vector: Optional[torch.nn.ParameterDict] = None # for DoRA
self._caches: dict[str, Any] = {}
self.kwargs = kwargsPEFT의 모든 LoraLayer 클래스에서 사용되는 __init__ 코드에는 모델을 초기화하는 데 사용되는 여러 매개변수가 있지만, 체크포인트 파일과 관련이 있는 것은 lora_A, lora_B, lora_embedding_A, lora_embedding_B 몇 가지뿐입니다. 이러한 매개변수는 클래스 속성 adapter_layer_names에 나열되어 있으며 학습 가능한 매개변수를 포함하므로 체크포인트 파일에 반드시 포함되어야 합니다. 랭크 r와 같은 나머지 모든 매개변수는 adapter_config.json에서 파생되므로 해당 파일에 포함되어야 합니다(기본값을 사용하는 경우를 제외하고).
BERT에 적용된 PEFT LoRA 모델의 state_dict를 확인해 보겠습니다. 기본 LoRA 설정으로 첫 번째 키 5개를 출력하면(나머지 키는 동일하며 레이어 번호만 다름) 다음과 같은 결과가 나옵니다:
base_model.model.encoder.layer.0.attention.self.query.lora_A.weightbase_model.model.encoder.layer.0.attention.self.query.lora_B.weightbase_model.model.encoder.layer.0.attention.self.value.lora_A.weightbase_model.model.encoder.layer.0.attention.self.value.lora_B.weightbase_model.model.encoder.layer.1.attention.self.query.lora_A.weight- 등
Let’s break this down:
- 기본적으로 BERT 모델의 경우, LoRA는 attention 모듈의
query및value레이어에 적용됩니다. 따라서 각 레이어의 키 이름에서attention.self.query와attention.self.value를 볼 수 있습니다. - LoRA는 가중치를 두 개의 저랭크 행렬인
lora_A와lora_B로 분해합니다. 키 이름에서lora_A와lora_B가 유래한 이유입니다. - 이 LoRA 행렬은
nn.Linear레이어로 구현되어 있으므로, 매개변수는.weight속성에 저장됩니다(lora_A.weight,lora_B.weight). - 기본적으로 LoRA는 BERT의 임베딩 레이어에 적용되지 않으므로
lora_A_embedding과lora_B_embedding에 대한 항목이 없습니다. state_dict의 키는 항상"base_model.model."로 시작합니다. 그 이유는 PEFT에서 기본 모델을 튜너 전용 모델(이 경우LoraModel)로 감싸고, 이 모델이 다시 일반적인 PEFT 모델(PeftModel)로 감싸기 때문입니다. 이 때문에 두 개의 접두사가 키에 추가됩니다. PEFT 형식으로 변환할 때 이러한 접두사를 추가하는 것이 필요합니다.
마지막 점은 prompt tuning과 같은 prefix tuning 기법에는 해당하지 않습니다. 이러한 기법에서는 추가된 임베딩이 키에 접두사가 추가되지 않은 채로
state_dict에 직접 저장됩니다.
로드된 모델의 매개변수 이름을 검사하면, base_model.model.encoder.layer.0.attention.self.query.lora_A.default.weight처럼 약간 다르게 보일 수 있어 놀라울 수 있습니다. 차이는倒数 두 번째 세그먼트의 .default 부분에 있습니다. 이 부분은 PEFT가 일반적으로 여러 어댑터를 한 번에 추가하는 것을 허용하기 때문에 존재합니다(nn.ModuleDict 또는 nn.ParameterDict를 사용하여 저장). 예를 들어, “other”라는 이름의 다른 어댑터를 추가하면 해당 어댑터의 키는 base_model.model.encoder.layer.0.attention.self.query.lora_A.other.weight가 됩니다.
save_pretrained()를 호출하면 어댑터 이름이 키에서 제거됩니다. 그 이유는 어댑터 이름이 모델 아키텍처의 중요한 부분이 아니라 임의의 이름이기 때문입니다. 어댑터를 로드할 때 완전히 다른 이름을 선택해도 모델은 동일한 방식으로 작동합니다. 따라서 어댑터 이름은 체크포인트 파일에 저장되지 않습니다.
save_pretrained("some/path")를 호출하고 어댑터 이름이"default"가 아닌 경우, 해당 어댑터는 어댑터 이름과 동일한 이름의 하위 디렉터리에 저장됩니다. 따라서 이름이 “other”인 경우some/path/other안에 저장됩니다.
일부 상황에서는 체크포인트 파일에 어떤 값을 추가할지 결정하는 것이 다소 복잡해질 수 있습니다. 예를 들어, PEFT에서 DoRA는 LoRA의 특수한 경우로 구현됩니다. DoRA 모델을 PEFT로 변환하려면 DoRA에 대한 추가 항목이 포함된 LoRA 체크포인트를 생성해야 합니다. 이전 LoraLayer 코드의 __init__에서 이를 확인할 수 있습니다:
self.lora_magnitude_vector: Optional[torch.nn.ParameterDict] = None # for DoRA이는 DoRA의 각 레이어마다 선택적 추가 매개변수가 있음을 나타냅니다.
adapter_config
PEFT 모델을 로드하는 데 필요한 나머지 모든 정보는 adapter_config.json 파일에 포함되어 있습니다. BERT에 적용된 LoRA 모델의 이 파일을 확인해 보겠습니다:
{
"alpha_pattern": {},
"auto_mapping": {
"base_model_class": "BertModel",
"parent_library": "transformers.models.bert.modeling_bert"
},
"base_model_name_or_path": "bert-base-uncased",
"bias": "none",
"fan_in_fan_out": false,
"inference_mode": true,
"init_lora_weights": true,
"layer_replication": null,
"layers_pattern": null,
"layers_to_transform": null,
"loftq_config": {},
"lora_alpha": 8,
"lora_dropout": 0.0,
"megatron_config": null,
"megatron_core": "megatron.core",
"modules_to_save": null,
"peft_type": "LORA",
"r": 8,
"rank_pattern": {},
"revision": null,
"target_modules": [
"query",
"value"
],
"task_type": null,
"use_dora": false,
"use_rslora": false
}여기에는 많은 항목이 포함되어 있어, 처음에는 어떤 값을 넣어야 할지 파악하는 것이 부담스럽게 느껴질 수 있습니다. 그러나 모델 로딩을 위해 대부분의 항목은 필수적이지 않습니다. 이는 기본값을 사용하므로 별도로 추가할 필요가 없거나, LoRA 가중치 초기화에만 영향을 미치며 모델 로딩과는 무관하기 때문입니다. 특정 파라미터의 역할이 명확하지 않다면, 예를 들어 "use_rslora",와 같은 경우, 해당 항목을 추가하지 않아도 됩니다. 또한 옵션이 추가됨에 따라 이 파일에는 향후 더 많은 항목이 포함될 수 있지만, 하위 호환성은 유지됩니다.
최소한 다음 항목을 포함해야 합니다:
{
"target_modules": ["query", "value"],
"peft_type": "LORA"
}그러나, 랭크 r나 base_model_name_or_path(Transformers 모델인 경우)처럼 가능한 한 많은 항목을 추가하는 것이 권장됩니다. 이러한 정보는 다른 사람이 모델을 더 잘 이해하고 쉽게 공유하는 데 도움이 됩니다. 예상되는 키와 값이 무엇인지 확인하려면 PEFT 소스 코드의 config.py 파일(예를 들어, 이는 LoRA의 config 파일입니다)을 확인해 보세요.
모델 저장
일부 상황에서는 기본 가중치를 포함하여 PEFT 모델 전체를 저장하고 싶을 수 있습니다. 예를 들어, PEFT 모델을 로드하려는 사용자가 기본 모델에 접근할 수 없는 경우 이렇게 해야 할 수 있습니다. 먼저 가중치를 병합하거나 Transformer 모델로 변환할 수 있습니다.
가중치 병합
PEFT 모델을 전체적으로 저장하는 가장 직관적인 방법은 어댑터 가중치를 베이스 가중치에 병합하는 것입니다:
merged_model = model.merge_and_unload() merged_model.save_pretrained(...)
다만, 이 접근 방식에는 몇 가지 단점이 있습니다:
- merge_and_unload()이 호출되면 PEFT 전용 기능이 없는 기본 모델이 반환됩니다. 따라서 더 이상 PEFT 전용 메서드를 사용할 수 없습니다.
- 가중치를 병합 해제하거나, 여러 어댑터를 동시에 로드하거나, 어댑터를 비활성화하는 등의 작업은 수행할 수 없습니다.
- 모든 PEFT 방법이 가중치 병합을 지원하지는 않습니다.
- 일부 PEFT 방법은 일반적으로 병합을 허용하지만, 특정 설정에서는 허용하지 않을 수 있습니다(예: 특정 양자화 기법을 사용하는 경우).
- PEFT 모델에는 기본 가중치도 모두 포함되므로 전체 모델은 PEFT 모델보다 훨씬 커집니다.
하지만 병합된 모델로 추론을 수행하면 속도가 약간 더 빨라야 합니다.
Transformers 모델로 변환
기본 모델이 Transformers 모델이라고 가정할 때, 전체 모델을 저장하는 또 다른 방법은 PEFT 가중치를 기본 모델에 직접 삽입하여 저장하는 이 해킹 방식입니다. 이 방식은 Transformers가 PEFT 모델이 PEFT 모델이 아니라고 믿도록 “속여야” 작동하며, 다른 어댑터는 Transformers에 구현되어 있지 않으므로 LoRA에서만 작동합니다.
model = ... # the PEFT model
...
# after you finish training the model, save it in a temporary location
model.save_pretrained(<temp_location>)
# now load this model directly into a transformers model, without the PEFT wrapper
# the PEFT weights are directly injected into the base model
model_loaded = AutoModel.from_pretrained(<temp_location>)
# now make the loaded model believe that it is _not_ a PEFT model
model_loaded._hf_peft_config_loaded = False
# now when we save it, it will save the whole model
model_loaded.save_pretrained(<final_location>)
# or upload to Hugging Face Hub
model_loaded.push_to_hub(<final_location>)