PEFT documentation
LoRA 변환
LoRA 변환
LoRA가 아닌 PEFT 모델을 LoRA 모델로 변환할 수 있는 함수들입니다.
설명
PEFT는 수십 가지의 다양한 매개변수 효율적 파인튜닝 기법을 지원합니다. 그중에서도 가장 인기 있는 기법은 단연 LoRA입니다. 이는 많은 다른 패키지들도 LoRA를 지원한다는 것을 의미합니다. 예를 들어, Diffusers는 LoRA 어댑터를 로드하여 확산 모델의 기능을 변경할 수 있게 해줍니다. vLLM은 LoRA 어댑터를 적용한 모델을 서빙할 수 있습니다. 이는 좋은 점이지만 불행히도, LoRA가 아닌 다른 PEFT 방법들은 거의 지원되지 않습니다. 따라서 특정 사용 사례에 대해 다른 PEFT 방법이 더 잘 작동할 수 있음에도 불구하고, 하위 패키지가 이를 지원하지 않아 사용할 수 없는 상황이 발생할 수 있습니다.
여기서 잠재적인 해결책을 제시합니다. PEFT는 PEFT 어댑터를 LoRA 어댑터로 변환할 수 있는 두 가지 함수, save_as_lora()와 convert_to_lora()를 제공합니다. 현재 모든 PEFT 방법이 이를 지원하지는 않지만, 지원하는 경우 가장 적합한 PEFT 방법으로 시작하여 나중에 LoRA 어댑터인 것처럼 사용할 수 있습니다.
예시
변환된 어댑터의 LoRA 랭크는 rank 인수에 0보다 큰 int를 전달하여 고정 랭크로 설정하거나, rank 인수에 0과 1 사이의 float를 전달하여 각 레이어에 맞게 조정되는 동적 랭크로 설정할 수 있습니다. 동적 랭크는 더 효율적일 수 있습니다(파라미터 수가 적으면서도 동일한 성능을 유지).
고정된 LoRA 랭크
save_as_lora()의 사용법은 비교적 간단합니다:
from peft import get_peft_model, save_as_lora
# first load and train your non-LoRA PEFT model as normal
base_model = ...
non_lora_config = ...
model = get_peft_model(base_model, non_lora_config)
# check that this PEFT method can indeed be converted to LoRA
assert model.supports_lora_conversion()
... # train the model
# the rank of the LoRA adapter that you want to convert to
target_rank = 64
# save as a LoRA checkpoint
save_as_lora(output_path, model, rank=target_rank)이렇게 하면 output_path에 LoRA 체크포인트가 생성되며, 다른 LoRA 어댑터와 마찬가지로 로드하거나 Diffusers나 vLLM과 같은 다운스트림 패키지에 사용할 수 있습니다.
변환된 LoRA 어댑터를 저장하지 않고 변환된 가중치를 바로 사용해야 하는 경우, 예를 들어 평가를 수행해야 하는 경우 convert_to_lora() 함수가 유용합니다:
from peft import convert_to_lora, get_peft_model, set_peft_model_state_dict
base_model = ...
non_lora_config = ...
model = get_peft_model(base_model, non_lora_config)
... # train the model
# get the lora config and state dict of the converted lora model
lora_config, lora_state_dict = convert_to_lora(model, rank=target_rank)
# reload the base model, or use model.unload()
base_model = ...
# apply the lora config to the base model
lora_model = get_peft_model(base_model, lora_config)
# load the LoRA weights onto the base model
set_peft_model_state_dict(lora_model, state_dict)동적 LoRA 랭크
위 예시에서는 변환에 고정된 LoRA rank를 사용했습니다. 그러나 일부 레이어는 정확한 변환에 높은 rank가 필요하지 않은 반면, 다른 레이어는 더 높은 rank가 필요할 수 있습니다. 이를 위해 PEFT는 rank 인수로 0과 1 사이의 float 값을 전달할 수 있는 옵션을 제공합니다. rank=0.5를 전달한다고 가정해 보겠습니다. 이는 각 레이어에 대해, 원래 어댑터가 가중치에 도입한 분산의 50%를 설명하도록 LoRA 어댑터의 rank가 선택됨을 의미합니다. 더 기술적인 표현으로, 내부적으로 어댑터의 가중치 기여도에 대해 Singular Value Decomposition를 수행한 후, 정규화했을 때 전달된 값의 합이 되도록 상위 특이값을 선택합니다.
# set a dynamic rank by passing a float
threshold = 0.7
# save as a LoRA checkpoint
save_as_lora(output_path, model, rank=threshold)
# get the lora config and state dict directly:
lora_config, lora_state_dict = convert_to_lora(model, rank=threshold)
# inspect the different ranks per layer:
print(lora_config.rank_pattern)이러한 동적 LoRA 랭크 유형은 각 레이어의 기여도가 크게 달라질 때 유용할 수 있습니다. 단점으로는 일부 레이어의 LoRA 랭크가 매우 높아져 메모리 사용량이 급증할 수 있다는 점입니다. 사용 사례에 가장 적합한 설정을 테스트해 보시기 바랍니다.
모델 컴파일
모델이 크면 변환에 시간이 걸릴 수 있습니다. 예를 들어 각 PEFT 모듈은 SVD 계산을 거쳐야 합니다. save_as_lora() 또는 convert_to_lora()에 compile_kwargs를 전달하면 변환 함수에 torch.compile을 적용하여 프로세스를 잠재적으로 가속화할 수 있습니다. compile_kwargs는 torch.compile에 전달되는 키워드 인자의 딕셔너리입니다(빈 딕셔너리도 작동합니다). 아래는 예시입니다:
compile_kwargs = {"dynamic": True, "mode": "max-autotune-no-cudagraphs", "fullgraph": True}
save_as_lora(output_path, model, rank=rank, compile_kwargs=compile_kwargs)LoRA에서 LoRA로 변환
LoRA 어댑터를 다른 LoRA 어댑터로 변환하는 것도 가능합니다. 왜 그렇게 하고 싶을까요? 하나의 이유가 있습니다. 바로 LoRA 어댑터의 랭크를 줄이고 싶을 때입니다. 학습 후 LoRA 어댑터를 축소하려면 save_as_lora() 또는 convert_to_lora()를 사용하고 더 작은 랭크를 전달하세요. 그러면 메모리와 저장 공간 사용량이 더 작은 새로운 LoRA 어댑터를 얻을 수 있습니다.
Metrics
Non-LoRA에서 LoRA로 변환
물론, 하나의 PEFT 어댑터를 다른 어댑터로 변환하는 과정은 손실이 발생하는 과정입니다. 새로운 어댑터는 초기 어댑터만큼 성능을 발휘하지 못할 가능성이 높습니다. 따라서 변환된 LoRA 어댑터를 평가하는 것이 강력히 권장됩니다. 이렇게 하면 변환된 어댑터가 사용 사례에 충분히 적합한지 확인할 수 있습니다. LoRA 어댑터의 랭크가 높을수록 초기 어댑터를 더 잘 근사한다는 일반적인 규칙이 적용됩니다. 이는 변환된 LoRA 어댑터가 유사한 성능을 달성하려면 원래 어댑터보다 더 많은 매개변수가 필요할 수 있음을 의미합니다.
예를 들어, PEFT MetaMathQA 벤치마크에서 도출된 수치를 살펴보겠습니다. 이를 위해 LoHa 어댑터를 사용하여 meta-llama/Llama-3.2-3B를 MetaMathQA로 파인튜닝한 후 GSM8K에서 평가했습니다. 초기 LoKr 어댑터의 랭크는 32였으며, 학습 가능한 매개변수는 18,350,080개, 테스트 정확도는 41.85%였습니다. 평가에는 12.25 GB의 메모리가 필요했습니다. 체크포인트는 rank 값이 다른 여러 LoRA로 변환되었습니다. 그 결과는 다음과 같습니다:
| rank | 학습 가능한 매개변수 | 테스트 정확도(%) | 정확도 변화 | 예약된 메모리 (최대, GB) | 메모리 증가 |
|---|---|---|---|---|---|
| 8 | 2293760 | 37.60 | -4.25 | 12.41 | 0.16 |
| 16 | 4587520 | 38.89 | -2.96 | 12.15 | -0.10 |
| 32 | 9175040 | 40.11 | -1.74 | 12.41 | 0.16 |
| 64 | 18350080 | 39.20 | -2.65 | 12.18 | -0.07 |
| 0.4 | 2428928 | 37.60 | -4.25 | 12.41 | 0.16 |
| 0.5 | 4761600 | 40.18 | -1.67 | 12.41 | 0.16 |
| 0.6 | 8857600 | 39.42 | -2.43 | 12.41 | 0.16 |
| 0.7 | 16230400 | 39.04 | -2.81 | 12.15 | -0.10 |
확인할 수 있듯이, 랭크가 충분히 높으면 원래 LoHa 어댑터에 근접한 테스트 정확도를 달성할 수 있습니다. 적절한 랭크를 선택하는 것은 모델 성능과 모델 효율성 사이의 트레이드오프입니다. 이 실험을 재현하려면 https://github.com/huggingface/peft/tree/main/scripts/evaluate-lora-conversion.py의 스크립트를 따르세요.
학습 가능한 매개변수의 수는 메모리 사용량으로 1:1로 환산할 수 없습니다. 학습 가능한 매개변수의 수가 동일하더라도 PEFT 방법에 따라 더 많은 메모리가 필요하거나 더 적은 메모리가 필요한 경우가 있습니다. 따라서 변환 후 LoRA 어댑터의 매개변수 수가 원래보다 많아도 서빙 시 메모리 효율이 더 높을 수 있습니다.
LoRA에서 LoRA로 변환
위 실험과 유사하게, LoRA에서 LoRA로의 변환(즉, LoRA 압축)도 평가할 수 있습니다. 여기서는 동일한 설정에서 RS-LoRA로 학습된 랭크 64의 LoRA 어댑터로 시작합니다. 초기 어댑터는 18,350,080개의 학습 가능 매개변수를 가지며, 테스트 정확도는 52.92%이고, 평가에 12.58 GB의 메모리가 필요합니다. 다음 표는 이 어댑터를 더 작은 랭크의 LoRA 어댑터로 변환한 결과를 보여줍니다.
| rank | 학습 가능한 매개변수 | 테스트 정확도(%) | 정확도 변화 | 예약된 메모리 (최대, GB) | 메모리 증가 |
|---|---|---|---|---|---|
| 8 | 2293760 | 43.37 | -9.55 | 12.38 | -0.20 |
| 16 | 4587520 | 48.90 | -4.02 | 12.38 | -0.20 |
| 32 | 9175040 | 51.48 | -1.44 | 12.49 | -0.09 |
| 48 | 13762560 | 52.01 | -0.91 | 12.38 | -0.20 |
| 0.5 | 2150400 | 44.12 | -8.80 | 12.37 | -0.21 |
| 0.6 | 3082240 | 47.54 | -5.38 | 12.37 | -0.21 |
| 0.7 | 4448256 | 50.49 | -2.43 | 12.37 | -0.21 |
| 0.8 | 6510592 | 50.11 | -2.81 | 12.37 | -0.21 |
| 0.9 | 10022912 | 51.55 | -1.37 | 12.38 | -0.20 |
| 0.95 | 12976128 | 52.62 | -0.30 | 12.39 | -0.19 |
예를 들어 rank 0.95의 경우, 매개변수 수를 30% 줄이면서 정확도 격차를 0.3%p까지 줄일 수 있습니다. 또한 이러한 압축된 LoRA는 낮은 rank에서 직접 학습하는 것보다 더 나은 성능을 보일 수 있습니다. 예를 들어 rank 32에서 직접 학습하면 테스트 정확도가 48.22%인 반면, rank 64에서 변환하면 51.48%가 됩니다.
주의 사항
LoRA 변환에는 몇 가지 제한 사항이 있습니다. 위에서 언급했듯이 성능 저하가 예상되며, 변환된 LoRA는 원래 어댑터보다 파라미터 효율이 낮을 가능성이 높습니다. 또한, LoRA 변환에는 다음과 같은 제한 사항이 있습니다:
- 현재 선형 레이어에 적용된 어댑터만 변환할 수 있습니다.
- 현재 모든 PEFT 방법이 LoRA 변환을 지원하지는 않습니다.
LoRA 변환 확장 수요가 많다면 GitHub discussion을 통해 알려 주십시오. 더 많은 레이어 유형과 PEFT 메서드와 호환되도록 작업을 진행하겠습니다.
API
LoRA가 아닌 모델을 LoRA 모델로 변환하고, LoraConfig와 state_dict를 반환합니다
peft.convert_to_lora
< source >( model: Modulerank: floatadapter_name: str = 'default'progressbar: bool = Falsecompile_kwargs = None )
매개변수
- model — 변환할 모델입니다. 변환을 지원하는 PEFT 레이어를 가진 모델이어야 합니다.
- rank (
int또는float) — 반환되는 LoRA 어댑터에 대한 원하는 랭크입니다. 랭크가 높을수록 원래 어댑터를 더 정확하게 반영하는 LoRA 어댑터가 생성됩니다. 그러나 메모리, 계산 및 디스크 공간 사용량도 증가합니다. 따라서 사용 사례에 가장 적합한 절충점을 나타내는 값을 선택하고 최종 어댑터를 검증하십시오. float 값이 전달되면 설명된 분산 / 에너지 임계값으로 해석됩니다. 상위 k개의 특이값이 전체 제곱 특이값의 해당 비율 이상을 차지하는 가장 작은 랭크 k를 선택합니다. 이는 일부 특이값만 이 레이어의 적응을 포착할 수 있는 경우 더 낮은 랭크가 할당되도록 합니다. float 값이 낮을수록 랭크도 낮아지고 그 반대도 성립합니다. 동적 랭크는 레이어별 랭크가 매우 불균형해져 일부 레이어가 활성화에 대해 비례적으로 높은 양의 메모리를 필요로 할 수 있음을 유의하십시오. 예측 가능한 메모리 요구 사항을 달성하려면 고정된(int) 랭크를 선택하는 것이 좋습니다. - adapter_name (
str, 선택 사항) — 변환할 어댑터의 이름입니다. 한 번에 하나의 어댑터만 변환할 수 있습니다. 기본값은"default"입니다. - progressbar (
bool) — 변환 진행 상황을 나타내는 progressbar를 표시할지 여부입니다. (큰 모델의 경우 몇 분 정도 걸릴 수 있습니다.) - compile_kwargs (
dict, 선택 사항) — 제공된 경우, 주어진 kwargs를torch.compile에 전달하여 개별 모듈을 LoRA로 변환하는 함수를 컴파일합니다. 이를 통해 대규모 모델의 변환 속도를 높일 수 있습니다.
PEFT 레이어가 있는 비-LoRA 모델을 LoRA 체크포인트로 변환합니다.
이는 동등한 변환을 허용하는 일부 특정 PEFT 메서드에서만 지원됩니다. 기본적으로 이는 델타 가중치로 베이스 가중치를 업데이트하는 방식으로 작동하는 PEFT 메서드에 해당합니다. 또한, 현재는 선형 레이어만 지원됩니다.
LoRA 어댑터는 초기 어댑터를 최대한 근사하도록 시도합니다. 랭크가 높을수록 근사도가 좋아집니다. 근사 결과가 원래 어댑터의 전체 성능에 도달하지 못할 것이며, LoRA 어댑터의 매개변수 효율이 원래 어댑터보다 낮을 것(즉, 유사한 성능을 위해 더 많은 매개변수가 필요할 것)으로 예상됩니다. 이러한 변환은 여전히 많은 상황에서 유용할 수 있습니다:
- PEFT에서 LoRA는 대부분의 다른 방법보다 더 많은 기능을 지원하며, 예를 들어 혼합 어댑터 배치(mixed adapter batches)를 지원합니다. 따라서 변환된 어댑터는 이러한 기능과 함께 사용할 수 있습니다.
- 일부 하위 패키지는 LoRA 어댑터는 지원하지만 다른 PEFT 방법은 지원하지 않습니다. 예를 들어 Diffusers가 그렇습니다. 이 변환을 통해 해당 패키지에서 LoRA가 아닌 어댑터를 사용할 수 있습니다.
LoRA 스케일링 팩터는 이미 LoRA 가중치에 포함되어 있으므로, 스케일링은 항상 1이 됩니다(즉, rank와 alpha가 동일하도록 선택됩니다).
참고: 이 함수는 아직 샤드된 모델을 지원하지 않습니다.
Raises
TypeError:
제공된 모델에 LoRA로 변환할 수 있는 레이어가 없는 경우 TypeError가 발생합니다.
ValueError:
잘못된 rank가 선택된 경우(너무 높거나 너무 낮은 경우).
LoRA가 아닌 모델을 LoRA 모델로 변환하고, 어댑터 체크포인트와 구성을 지정된 경로에 저장합니다
peft.save_as_lora
< source >( path: str | os.PathLikemodel: Modulerank: floatadapter_name: str = 'default'progressbar: bool = Falsecompile_kwargs = None )
매개변수
- model — 변환할 모델입니다. 변환을 지원하는 PEFT 레이어를 가진 모델이어야 합니다.
- rank (
int또는float) — 반환되는 LoRA 어댑터에 대한 원하는 랭크입니다. 랭크가 높을수록 원래 어댑터를 더 정확하게 반영하는 LoRA 어댑터가 생성됩니다. 그러나 메모리, 계산 및 디스크 공간 사용량도 증가합니다. 따라서 사용 사례에 가장 적합한 절충점을 나타내는 값을 선택하고 최종 어댑터를 검증하십시오. float 값이 전달되면 설명된 분산 / 에너지 임계값으로 해석됩니다. 상위 k개의 특이값이 전체 제곱 특이값의 해당 비율 이상을 차지하는 가장 작은 랭크 k를 선택합니다. 이는 일부 특이값만 이 레이어의 적응을 포착할 수 있는 경우 더 낮은 랭크가 할당되도록 합니다. float 값이 낮을수록 랭크도 낮아지고 그 반대도 성립합니다. 동적 랭크는 레이어별 랭크가 매우 불균형해져 일부 레이어가 활성화에 대해 비례적으로 높은 양의 메모리를 필요로 할 수 있음을 유의하십시오. 예측 가능한 메모리 요구 사항을 달성하려면 고정된(int) 랭크를 선택하는 것이 좋습니다. - adapter_name (
str, 선택 사항) — 변환할 어댑터의 이름입니다. 한 번에 하나의 어댑터만 변환할 수 있습니다. 기본값은"default"입니다. - progressbar (
bool) — 변환 진행 상황을 나타내는 progressbar를 표시할지 여부입니다. (큰 모델의 경우 몇 분 정도 걸릴 수 있습니다.) - compile_kwargs (
dict, 선택 사항) — 제공된 경우, 주어진 kwargs를torch.compile에 전달하여 개별 모듈을 LoRA로 변환하는 함수를 컴파일합니다. 이를 통해 대규모 모델의 변환 속도를 높일 수 있습니다.
PEFT 레이어가 있는 비-LoRA 모델을 LoRA로 변환한 다음, 체크포인트 파일과 PEFT 구성을 저장합니다.
이는 동등한 변환을 허용하는 일부 특정 PEFT 메서드에서만 지원됩니다. 기본적으로 이는 델타 가중치로 베이스 가중치를 업데이트하는 방식으로 작동하는 PEFT 메서드에 해당합니다. 또한, 현재는 선형 레이어만 지원됩니다.
LoRA 어댑터는 초기 어댑터를 최대한 근사하도록 시도합니다. 랭크가 높을수록 근사도가 좋아집니다. 근사 결과가 원래 어댑터의 전체 성능에 도달하지 못할 것이며, LoRA 어댑터의 매개변수 효율이 원래 어댑터보다 낮을 것(즉, 유사한 성능을 위해 더 많은 매개변수가 필요할 것)으로 예상됩니다. 이러한 변환은 여전히 많은 상황에서 유용할 수 있습니다:
- PEFT에서 LoRA는 대부분의 다른 방법보다 더 많은 기능을 지원하며, 예를 들어 혼합 어댑터 배치(mixed adapter batches)를 지원합니다. 따라서 변환된 어댑터는 이러한 기능과 함께 사용할 수 있습니다.
- 일부 하위 패키지는 LoRA 어댑터는 지원하지만 다른 PEFT 방법은 지원하지 않습니다. 예를 들어 Diffusers가 그렇습니다. 이 변환을 통해 해당 패키지에서 LoRA가 아닌 어댑터를 사용할 수 있습니다.
LoRA 스케일링 팩터는 이미 LoRA 가중치에 포함되어 있으므로, 스케일링은 항상 1이 됩니다(즉, rank와 alpha가 동일하도록 선택됩니다).
변환된 LoRA 가중치를 다음과 같이 로드할 수 있습니다:
>>> lora_path = ...
>>> save_as_lora(lora_path, model, rank=...)
>>> base_model = AutoModel.from_pretrained(...)
>>> lora_model = PeftModel.from_pretrained(base_model, lora_path)참고: 이 함수는 아직 샤드된 모델을 지원하지 않습니다.
Raises
TypeError:
제공된 모델에 LoRA로 변환할 수 있는 레이어가 없는 경우 TypeError가 발생합니다.
ValueError:
잘못된 rank가 선택된 경우(너무 높거나 너무 낮은 경우).