|
| 1 | +<!--Copyright 2024 The HuggingFace Team. All rights reserved. |
| 2 | +
|
| 3 | +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
| 4 | +the License. You may obtain a copy of the License at |
| 5 | +
|
| 6 | +http://www.apache.org/licenses/LICENSE-2.0 |
| 7 | +
|
| 8 | +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
| 9 | +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
| 10 | +
|
| 11 | +⚠️ Note that this file is in Markdown but contain specific syntax for our doc-builder (similar to MDX) that may not be |
| 12 | +rendered properly in your Markdown viewer. |
| 13 | +
|
| 14 | +--> |
| 15 | + |
| 16 | +# 모델 구성 요소 맞춤 설정하기[[customizing-model-components]] |
| 17 | + |
| 18 | +모델을 완전히 새로 작성하는 대신 구성 요소를 수정하여 모델을 맞춤 설정하는 방법이 있습니다. 이 방법으로 모델을 특정 사용 사례에 맞게 모델을 조정할 수 있습니다. 예를 들어, 새로운 레이어를 추가하거나 아키텍처의 어텐션 메커니즘을 최적화할 수 있습니다. 이러한 맞춤 설정은 트랜스포머 모델에 직접 적용되므로, [`Trainer`], [`PreTrainedModel`] 및 [PEFT](https://huggingface.co/docs/peft/en/index) 라이브러리와 같은 기능을 계속 사용할 수 있습니다. |
| 19 | + |
| 20 | +이 가이드에서는 모델의 어텐션 메커니즘을 맞춤 설정하여 [Low-Rank Adaptation (LoRA)](https://huggingface.co/docs/peft/conceptual_guides/adapter#low-rank-adaptation-lora)를 적용하는 방법을 설명합니다. |
| 21 | + |
| 22 | +> [!TIP] |
| 23 | +> 모델 코드를 반복적으로 수정하고 개발할 때 [clear_import_cache](https://github.com/huggingface/transformers/blob/9985d06add07a4cc691dc54a7e34f54205c04d40/src/transformers/utils/import_utils.py#L2286) 유틸리티가 매우 유용합니다. 이 기능은 캐시된 모든 트랜스포머 모듈을 제거하여 Python이 환경을 재시작하지 않고도 수정된 코드를 다시 가져올 수 있도록 합니다. |
| 24 | +> |
| 25 | +> ```py |
| 26 | +> from transformers import AutoModel |
| 27 | +> from transformers.utils.import_utils import clear_import_cache |
| 28 | +> |
| 29 | +> model = AutoModel.from_pretrained("bert-base-uncased") |
| 30 | +> # 모델 코드 수정 |
| 31 | +> # 캐시를 지워 수정된 코드를 다시 가져오기 |
| 32 | +> clear_import_cache() |
| 33 | +> # 업데이트된 코드를 사용하기 위해 다시 가져오기 |
| 34 | +> model = AutoModel.from_pretrained("bert-base-uncased") |
| 35 | +> ``` |
| 36 | +
|
| 37 | +## 어텐션 클래스[[attention-class]] |
| 38 | +
|
| 39 | +[Segment Anything](./model_doc/sam)은 이미지 분할 모델로, 어텐션 메커니즘에서 query-key-value(`qkv`) 프로젝션을 결합합니다. 학습 가능한 파라미터 수와 연산 부담을 줄이기 위해 `qkv` 프로젝션에 LoRA를 적용할 수 있습니다. 이를 위해서는 `qkv` 프로젝션을 분리하여 `q`와 `v`에 LoRA를 개별적으로 적용해야 합니다. |
| 40 | +
|
| 41 | +1. 원래의 `SamVisionAttention` 클래스를 상속하여 `SamVisionAttentionSplit`이라는 사용자 정의 어텐션 클래스를 만듭니다. `__init__`에서 결합된 `qkv`를 삭제하고, `q`, `k`, `v`를 위한 개별 선형 레이어를 생성합니다. |
| 42 | +
|
| 43 | +```py |
| 44 | +import torch |
| 45 | +import torch.nn as nn |
| 46 | +from transformers.models.sam.modeling_sam import SamVisionAttention |
| 47 | +
|
| 48 | +class SamVisionAttentionSplit(SamVisionAttention, nn.Module): |
| 49 | + def __init__(self, config, window_size): |
| 50 | + super().__init__(config, window_size) |
| 51 | + # 결합된 qkv 제거 |
| 52 | + del self.qkv |
| 53 | + # q, k, v 개별 프로젝션 생성 |
| 54 | + self.q = nn.Linear(config.hidden_size, config.hidden_size, bias=config.qkv_bias) |
| 55 | + self.k = nn.Linear(config.hidden_size, config.hidden_size, bias=config.qkv_bias) |
| 56 | + self.v = nn.Linear(config.hidden_size, config.hidden_size, bias=config.qkv_bias) |
| 57 | + self._register_load_state_dict_pre_hook(self.split_q_k_v_load_hook) |
| 58 | +``` |
| 59 | +
|
| 60 | +2. `_split_qkv_load_hook` 함수는 모델을 가져올 때, 사전 훈련된 `qkv` 가중치를 `q`, `k`, `v`로 분리하여 사전 훈련된 모델과의 호환성을 보장합니다. |
| 61 | + |
| 62 | +```py |
| 63 | + def split_q_k_v_load_hook(self, state_dict, prefix, *args): |
| 64 | + keys_to_delete = [] |
| 65 | + for key in list(state_dict.keys()): |
| 66 | + if "qkv." in key: |
| 67 | + # 결합된 프로젝션에서 q, k, v 분리 |
| 68 | + q, k, v = state_dict[key].chunk(3, dim=0) |
| 69 | + # 개별 q, k, v 프로젝션으로 대체 |
| 70 | + state_dict[key.replace("qkv.", "q.")] = q |
| 71 | + state_dict[key.replace("qkv.", "k.")] = k |
| 72 | + state_dict[key.replace("qkv.", "v.")] = v |
| 73 | + # 기존 qkv 키를 삭제 대상으로 표시 |
| 74 | + keys_to_delete.append(key) |
| 75 | + |
| 76 | + # 기존 qkv 키 제거 |
| 77 | + for key in keys_to_delete: |
| 78 | + del state_dict[key] |
| 79 | +``` |
| 80 | + |
| 81 | +3. `forward` 단계에서 `q`, `k`, `v`는 개별적으로 계산되며, 어텐션 메커니즘의 나머지 부분은 동일하게 유지됩니다. |
| 82 | + |
| 83 | +```py |
| 84 | + def forward(self, hidden_states: torch.Tensor, output_attentions=False) -> torch.Tensor: |
| 85 | + batch_size, height, width, _ = hidden_states.shape |
| 86 | + qkv_shapes = (batch_size * self.num_attention_heads, height * width, -1) |
| 87 | + query = self.q(hidden_states).reshape((batch_size, height * width,self.num_attention_heads, -1)).permute(0,2,1,3).reshape(qkv_shapes) |
| 88 | + key = self.k(hidden_states).reshape((batch_size, height * width,self.num_attention_heads, -1)).permute(0,2,1,3).reshape(qkv_shapes) |
| 89 | + value = self.v(hidden_states).reshape((batch_size, height * width,self.num_attention_heads, -1)).permute(0,2,1,3).reshape(qkv_shapes) |
| 90 | + |
| 91 | + attn_weights = (query * self.scale) @ key.transpose(-2, -1) |
| 92 | + |
| 93 | + attn_weights = torch.nn.functional.softmax(attn_weights, dtype=torch.float32, dim=-1).to(query.dtype) |
| 94 | + attn_probs = nn.functional.dropout(attn_weights, p=self.dropout, training=self.training) |
| 95 | + attn_output = (attn_probs @ value).reshape(batch_size, self.num_attention_heads, height, width, -1) |
| 96 | + attn_output = attn_output.permute(0, 2, 3, 1, 4).reshape(batch_size, height, width, -1) |
| 97 | + attn_output = self.proj(attn_output) |
| 98 | + |
| 99 | + if output_attentions: |
| 100 | + outputs = (attn_output, attn_weights) |
| 101 | + else: |
| 102 | + outputs = (attn_output, None) |
| 103 | + return outputs |
| 104 | +``` |
| 105 | + |
| 106 | +사용자 정의 `SamVisionAttentionSplit` 클래스를 원본 모델의 `SamVisionAttention` 모듈에 할당하여 교체합니다. 모델 내 모든 `SamVisionAttention` 인스턴스는 분리된 어텐션 버전으로 대체됩니다. |
| 107 | + |
| 108 | +[`~PreTrainedModel.from_pretrained`]로 모델을 가져오세요. |
| 109 | + |
| 110 | +```py |
| 111 | +from transformers import SamModel |
| 112 | + |
| 113 | +# 사전 훈련된 SAM 모델 가져오기 |
| 114 | +model = SamModel.from_pretrained("facebook/sam-vit-base") |
| 115 | + |
| 116 | +# 비전-인코더 모듈에서 어텐션 클래스 교체 |
| 117 | +for layer in model.vision_encoder.layers: |
| 118 | + if hasattr(layer, "attn"): |
| 119 | + layer.attn = SamVisionAttentionSplit(model.config.vision_config, model.config.vision_config.window_size) |
| 120 | +``` |
| 121 | + |
| 122 | +## LoRA[[lora]] |
| 123 | + |
| 124 | +분리된 `q`, `k`, `v` 프로젝션을 사용할 때 , `q`와 `v`에 LoRA를 적용합니다. |
| 125 | + |
| 126 | +[LoraConfig](https://huggingface.co/docs/peft/package_reference/config#peft.PeftConfig)를 생성하고, 랭크 `r`, `lora_alpha`, `lora_dropout`, `task_type`, 그리고 가장 중요한 적용될 모듈을 지정합니다. |
| 127 | + |
| 128 | +```py |
| 129 | +from peft import LoraConfig, get_peft_model |
| 130 | + |
| 131 | +config = LoraConfig( |
| 132 | + r=16, |
| 133 | + lora_alpha=32, |
| 134 | + # q와 v에 LoRA 적용 |
| 135 | + target_modules=["q", "v"], |
| 136 | + lora_dropout=0.1, |
| 137 | + task_type="FEATURE_EXTRACTION" |
| 138 | +) |
| 139 | +``` |
| 140 | + |
| 141 | +모델과 [LoraConfig](https://huggingface.co/docs/peft/package_reference/config#peft.PeftConfig)를 [get\_peft\_model](https://huggingface.co/docs/peft/package_reference/peft_model#peft.get_peft_model)에 전달하여 모델에 LoRA를 적용합니다. |
| 142 | + |
| 143 | +```py |
| 144 | +model = get_peft_model(model, config) |
| 145 | +``` |
| 146 | + |
| 147 | +[print_trainable_parameters](https://huggingface.co/docs/peft/package_reference/peft_model#peft.PeftMixedModel.print_trainable_parameters)를 호출하여 전체 파라미터 수 대비 훈련되는 파라미터 수를 확인하세요. |
| 148 | + |
| 149 | +```py |
| 150 | +model.print_trainable_parameters() |
| 151 | +"trainable params: 589,824 || all params: 94,274,096 || trainable%: 0.6256" |
| 152 | +``` |
0 commit comments