PEFT documentation
Format checkpoint PEFT
Format checkpoint PEFT
Dokumen ini menjelaskan bagaimana file checkpoint PEPEFT disusun dan bagaimana cara mengonversi antara format PEFT dan format lainnya.
File PEFT
Metode PEFT (parameter-efficient fine-tuning) hanya memperbarui sebagian kecil parameter model, bukan semuanya. Ini menguntungkan karena file checkpoint umumnya jauh lebih kecil daripada file model asli, sehingga lebih mudah disimpan dan dibagikan. Namun, hal ini juga berarti bahwa untuk memuat model PEFT, Anda juga harus memiliki model aslinya.
Saat Anda memanggil save_pretrained() pada model PEFT, model PEFT menyimpan tiga file, yang dijelaskan di bawah ini:
adapter_model.safetensorsatauadapter_model.bin
Secara default, model disimpan dalam format safetensors, alternatif yang aman dari format bin, yang diketahui rentan terhadap kerentanan keamanan karena menggunakan utilitas pickle di baliknya. Namun, kedua format menyimpan state_dict yang sama dan dapat saling ditukar.
state_dict hanya berisi parameter dari modul adapter, bukan model dasar. Untuk mengilustrasikan perbedaan ukurannya, model BERT normal membutuhkan ruang disk sekitar 420MB, sedangkan adapter IA³ di atas model BERT ini hanya membutuhkan sekitar 260KB.
adapter_config.json
File adapter_config.json berisi konfigurasi modul adapter, yang diperlukan untuk memuat model. Di bawah ini adalah contoh adapter_config.json untuk adapter IA³ dengan pengaturan standar yang diterapkan pada model BERT:
{
"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
}File konfigurasi berisi:
- tipe modul adapter yang disimpan,
"peft_type": "IA3" - informasi tentang model dasar seperti
"base_model_name_or_path": "bert-base-uncased" - revisi model (jika ada),
"revision": null
Jika model dasar bukan model Transformers yang telah dilatih sebelumnya, dua entri terakhir akan menjadi null. Selain itu, semua pengaturan terkait dengan adapter IA³ spesifik yang digunakan untuk fine-tune model.
README.md
README.md yang dihasilkan adalah kartu model dari model PEFT dan berisi beberapa entri yang sudah terisi. Tujuannya adalah untuk memudahkan berbagi model dengan orang lain dan menyediakan informasi dasar tentang model. File ini tidak diperlukan untuk memuat model.
Konversi ke format PEFT
Saat mengonversi dari format lain ke format PEFT, kami membutuhkan file adapter_model.safetensors (atau adapter_model.bin) dan file adapter_config.json.
adapter_model
Untuk bobot model, penting untuk menggunakan pemetaan yang benar dari nama parameter ke nilai agar PEFT dapat memuat file. Mendapatkan pemetaan yang benar adalah latihan dalam memeriksa detail implementasi, karena tidak ada format yang disepakati secara umum untuk adapter PEFT.
Untungnya, menentukan pemetaan ini tidak terlalu rumit untuk kasus dasar yang umum. Mari kita lihat contoh konkretnya, yaitu 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 = kwargsDalam kode __init__ yang digunakan oleh semua kelas LoraLayer di PEFT, terdapat sejumlah parameter yang digunakan untuk menginisialisasi model, tetapi hanya beberapa yang relevan untuk file checkpoint: lora_A, lora_B, lora_embedding_A, dan lora_embedding_B. Parameter-parameter ini tercantum dalam atribut kelas adapter_layer_names dan berisi parameter yang dapat dipelajari, sehingga harus disertakan dalam file checkpoint. Semua parameter lainnya, seperti rank r, diturunkan dari adapter_config.json dan harus disertakan di sana (kecuali nilai default yang digunakan).
Mari kita periksa state_dict dari model PEFT LoRA yang diterapkan pada BERT. Saat mencetak lima kunci pertama menggunakan pengaturan LoRA default (kunci sisanya sama, hanya dengan nomor lapisan yang berbeda), hasilnya:
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- dst.
Mari kita uraikan ini:
- Secara default, untuk model BERT, LoRA diterapkan pada lapisan
querydanvaluedari modul perhatian. Inilah sebabnya Anda melihatattention.self.querydanattention.self.valuedalam nama kunci untuk setiap lapisan. - LoRA menguraikan bobot menjadi dua matriks ber-rank rendah,
lora_Adanlora_B. Inilah asallora_Adanlora_Bdalam nama kunci. - Matriks LoRA ini diimplementasikan sebagai lapisan
nn.Linear, sehingga parameter disimpan dalam atribut.weight(lora_A.weight,lora_B.weight). - Secara default, LoRA tidak diterapkan pada lapisan embedding BERT, sehingga tidak ada entri untuk
lora_A_embeddingdanlora_B_embedding. - Kunci dari
state_dictselalu diawali dengan"base_model.model.". Alasannya adalah, dalam PEFT, model dasar dibungkus di dalam model yang spesifik untuk tuner (LoraModeldalam kasus ini), yang pada gilirannya dibungkus di dalam model PEFT umum (PeftModel). Karena alasan ini, dua prefiks tersebut ditambahkan ke kunci. Saat mengonversi ke format PEFT, penambahan prefiks ini menjadi wajib.
Poin terakhir ini tidak berlaku untuk teknik penyesuaian awalan seperti penyesuaian prompt. Di sana, embedding tambahan disimpan langsung di dalam
state_dicttanpa ada awalan yang ditambahkan ke kunci.
Saat memeriksa nama parameter pada model yang dimuat, Anda mungkin terkejut melihat bahwa nama-nama tersebut tampak sedikit berbeda, misalnya base_model.model.encoder.layer.0.attention.self.query.lora_A.default.weight. Perbedaannya terletak pada bagian .default di segmen kedua dari akhir. Bagian ini ada karena PEFT umumnya memungkinkan penambahan beberapa adapter sekaligus (menggunakan nn.ModuleDict atau nn.ParameterDict untuk menyimpannya). Sebagai contoh, jika Anda menambahkan adapter lain bernama “other”, kunci untuk adapter tersebut akan menjadi base_model.model.encoder.layer.0.attention.self.query.lora_A.other.weight.
Saat Anda memanggil save_pretrained(), nama adapter dihapus dari kunci. Alasannya adalah nama adapter bukan bagian penting dari arsitektur model; itu hanya nama sewenang-wenang. Saat memuat adapter, Anda bisa memilih nama yang sama sekali berbeda, dan model akan tetap berfungsi dengan cara yang sama. Inilah mengapa nama adapter tidak disimpan dalam file checkpoint.
Jika Anda memanggil
save_pretrained("some/path")dan nama adapter bukan"default", adapter disimpan dalam sub-direktori dengan nama yang sama dengan adapter. Jadi, jika namanya adalah “other”, ia akan disimpan di dalamsome/path/other.
Dalam beberapa situasi, menentukan nilai mana yang harus ditambahkan ke file checkpoint bisa menjadi sedikit lebih rumit. Misalnya, dalam PEFT, DoRA diimplementasikan sebagai kasus khusus dari LoRA. Jika Anda ingin mengonversi model DoRA ke PEFT, Anda harus membuat checkpoint LoRA dengan entri tambahan untuk DoRA. Anda dapat melihat ini di __init__ dari kode LoraLayer sebelumnya:
self.lora_magnitude_vector: Optional[torch.nn.ParameterDict] = None # for DoRAIni menunjukkan bahwa ada parameter tambahan opsional per lapisan untuk DoRA.
adapter_config
Semua informasi lain yang diperlukan untuk memuat model PEFT terdapat dalam file adapter_config.json. Mari kita periksa file ini untuk model LoRA yang diterapkan pada BERT:
{
"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
}Ini berisi banyak entri, dan pada pandangan pertama, mungkin terasa membingungkan untuk menentukan semua nilai yang tepat untuk dimasukkan. Namun, sebagian besar entri tidak diperlukan untuk memuat model. Hal ini karena mereka menggunakan nilai default dan tidak perlu ditambahkan, atau karena mereka hanya memengaruhi inisialisasi bobot LoRA, yang tidak relevan saat memuat model. Jika Anda tidak tahu apa yang dilakukan oleh parameter tertentu, misalnya "use_rslora", jangan tambahkan, dan Anda seharusnya tidak akan mengalami masalah. Perhatikan juga bahwa seiring bertambahnya opsi, file ini akan memiliki lebih banyak entri di masa depan, tetapi seharusnya tetap kompatibel mundur.
Sebagai minimal, Anda harus menyertakan entri berikut:
{
"target_modules": ["query", "value"],
"peft_type": "LORA"
}Namun, disarankan untuk menambahkan sebanyak mungkin entri, seperti rank r atau base_model_name_or_path (jika itu adalah model Transformers). Informasi ini dapat membantu orang lain memahami model dengan lebih baik dan membagikannya dengan lebih mudah. Untuk memeriksa kunci dan nilai apa yang diharapkan, lihat file config.py (sebagai contoh, ini adalah file config untuk LoRA) di kode sumber PEFT.
Penyimpanan model
Dalam beberapa situasi, Anda mungkin ingin menyimpan seluruh model PEFT, termasuk bobot dasar. Hal ini dapat diperlukan jika, misalnya, model dasar tidak tersedia bagi pengguna yang mencoba memuat model PEFT. Anda dapat menggabungkan bobot terlebih dahulu atau mengonversinya menjadi model Transformer.
Gabungkan bobot
Cara paling sederhana untuk menyimpan seluruh model PEFT adalah dengan menggabungkan bobot adapter ke dalam bobot dasar:
merged_model = model.merge_and_unload() merged_model.save_pretrained(...)
Namun, pendekatan ini memiliki beberapa kerugian:
- Setelah merge_and_unload() dipanggil, Anda akan mendapatkan model dasar tanpa fungsionalitas spesifik PEFT. Ini berarti Anda tidak dapat lagi menggunakan metode-metode spesifik PEFT.
- Anda tidak dapat menggabungkan kembali bobot, memuat beberapa adapter sekaligus, menonaktifkan adapter, dan sebagainya.
- Tidak semua metode PEFT mendukung penggabungan bobot.
- Beberapa metode PEFT umumnya memungkinkan penggabungan, tetapi tidak dengan pengaturan tertentu (misalnya, saat menggunakan teknik kuantisasi tertentu).
- Seluruh model akan jauh lebih besar daripada model PEFT, karena akan berisi semua bobot dasar juga.
Namun, inferensi dengan model yang sudah digabungkan seharusnya sedikit lebih cepat.
Konversi ke model Transformers
Cara lain untuk menyimpan seluruh model, dengan asumsi model dasarnya adalah model Transformers, adalah menggunakan pendekatan yang agak janggal ini untuk langsung menyisipkan bobot PEFT ke dalam model dasar dan menyimpannya. Pendekatan ini hanya berhasil jika Anda “menipu” Transformers agar meyakini bahwa model PEFT tersebut bukan model PEFT. Ini hanya berfungsi dengan LoRA karena adapter lain tidak diimplementasikan di dalam Transformers.
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>)