PEFT documentation

Model kustom

You are viewing main version, which requires installation from source. If you'd like regular pip install, checkout the latest stable version (v0.21.0).
Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

Model kustom

Beberapa teknik fine-tuning, seperti prompt tuning, bersifat spesifik untuk model bahasa. Artinya, di 🤗 PEFT, diasumsikan bahwa model 🤗 Transformers yang digunakan. Namun, teknik fine-tuning lainnya - seperti LoRA - tidak terbatas pada tipe model tertentu.

Dalam panduan ini, kita akan melihat bagaimana LoRA dapat diterapkan pada multilayer perceptron, model computer vision dari pustaka timm, atau arsitektur 🤗 Transformers yang baru.

Perceptron berlapis

Mari kita asumsikan bahwa kita ingin melakukan fine-tuning pada multilayer perceptron dengan LoRA. Berikut adalah definisinya:

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)

Ini adalah multilayer perceptron sederhana dengan lapisan input, lapisan tersembunyi, dan lapisan output.

Untuk contoh sederhana ini, kami memilih jumlah unit tersembunyi yang sangat besar untuk menyoroti peningkatan efisiensi dari PEFT, namun peningkatan tersebut sejalan dengan contoh yang lebih realistis.

Ada beberapa lapisan linear dalam model ini yang dapat disetel dengan LoRA. Saat bekerja dengan model 🤗 Transformers yang umum, PEFT akan mengetahui lapisan mana yang harus diterapkan LoRA, tetapi dalam kasus ini, terserah kita sebagai pengguna untuk memilih lapisannya. Untuk menentukan nama lapisan yang akan disetel:

print([(n, type(m)) for n, m in MLP().named_modules()])

Ini seharusnya mencetak:

[('', __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)]

Misalnya, kita ingin menerapkan LoRA pada lapisan input dan lapisan tersembunyi, yaitu 'seq.0' dan 'seq.2'. Selain itu, misalkan kita ingin memperbarui lapisan output tanpa LoRA, yaitu 'seq.4'. Konfigurasi yang sesuai adalah:

from peft import LoraConfig

config = LoraConfig(
    target_modules=["seq.0", "seq.2"],
    modules_to_save=["seq.4"],
)

Dengan itu, kita dapat membuat model PEFT kita dan memeriksa fraksi parameter yang dilatih:

from peft import get_peft_model

model = MLP()
peft_model = get_peft_model(model, config)
peft_model.print_healthcheck()

Terakhir, kita dapat menggunakan framework pelatihan apa pun yang kita inginkan, atau menulis loop fit kita sendiri, untuk melatih peft_model.

Untuk contoh lengkap, lihat notebook ini.

model timm

Perpustakaan timm berisi sejumlah besar model computer vision yang sudah dilatih sebelumnya. Model-model tersebut juga dapat dihaluskan (fine-tuned) dengan PEFT. Mari kita lihat bagaimana hal ini bekerja dalam praktiknya.

Untuk memulai, pastikan timm terinstal di lingkungan Python:

python -m pip install -U timm

Selanjutnya, kita memuat model timm untuk tugas klasifikasi gambar:

import timm

num_classes = ...
model_id = "timm/poolformer_m36.sail_in1k"
model = timm.create_model(model_id, pretrained=True, num_classes=num_classes)

Sekali lagi, kita perlu memutuskan lapisan mana yang akan diterapkan LoRA. Karena LoRA mendukung lapisan konvolusi 2D, dan karena lapisan tersebut merupakan blok bangunan utama dari model ini, kita harus menerapkan LoRA pada lapisan konvolusi 2D. Untuk mengidentifikasi nama-nama lapisan tersebut, mari kita lihat semua nama lapisan:

print([(n, type(m)) for n, m in model.named_modules()])

Ini akan mencetak daftar yang sangat panjang, kami hanya akan menampilkan beberapa yang pertama:

[('', 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)]
 ]

Setelah diperiksa lebih dekat, kita melihat bahwa lapisan konvolusi 2D memiliki nama seperti "stages.0.blocks.0.mlp.fc1" dan "stages.0.blocks.0.mlp.fc2". Bagaimana cara mencocokkan nama lapisan tersebut secara spesifik? Anda dapat menulis regular expressions untuk mencocokkan nama lapisan. Untuk kasus kita, regex r".*\.mlp\.fc\d" seharusnya cukup.

Selain itu, seperti pada contoh pertama, kita harus memastikan bahwa lapisan output, dalam hal ini kepala klasifikasi, juga diperbarui. Melihat akhir dari daftar yang dicetak di atas, kita dapat melihat bahwa namanya adalah 'head.fc'. Dengan hal itu, berikut adalah konfigurasi LoRA kami:

config = LoraConfig(target_modules=r".*\.mlp\.fc\d", modules_to_save=["head.fc"])

Lalu kita hanya perlu membuat model PEFT dengan meneruskan model dasar dan konfigurasi kita ke get_peft_model:

peft_model = get_peft_model(model, config)
peft_model.print_healthcheck()

Hal ini menunjukkan bahwa kita hanya perlu melatih kurang dari 2% dari semua parameter, yang merupakan peningkatan efisiensi yang sangat besar.

Untuk contoh lengkap, lihat notebook ini.

Arsitektur transformers baru

Ketika arsitektur transformers populer baru dirilis, kami berusaha sebaik mungkin untuk segera menambahkannya ke PEFT. Jika Anda menemukan model transformers yang tidak didukung secara bawaan, jangan khawatir, model tersebut kemungkinan besar tetap akan berfungsi jika konfigurasinya diatur dengan benar. Secara spesifik, Anda harus mengidentifikasi lapisan yang perlu diadaptasi dan mengaturnya dengan benar saat menginisialisasi kelas konfigurasi yang sesuai, misalnya LoraConfig. Berikut adalah beberapa tips untuk membantu Anda.

Sebagai langkah pertama, adalah ide yang baik untuk memeriksa model yang sudah ada sebagai inspirasi. Anda dapat menemukannya di dalam constants.py di repositori PEFT. Sering kali, Anda akan menemukan arsitektur serupa yang menggunakan nama yang sama. Misalnya, jika arsitektur model baru adalah variasi dari model “mistral” dan Anda ingin menerapkan LoRA, Anda dapat melihat bahwa entri untuk “mistral” di TRANSFORMERS_MODELS_TO_LORA_TARGET_MODULES_MAPPING berisi ["q_proj", "v_proj"]. Ini memberi tahu Anda bahwa untuk model “mistral”, target_modules untuk LoRA seharusnya ["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)

Jika itu tidak membantu, periksa modul yang ada dalam arsitektur model Anda dengan metode named_modules dan coba identifikasi lapisan perhatian, terutama lapisan kunci, kueri, dan nilai. Lapisan tersebut sering kali memiliki nama seperti c_attn, query, q_proj, dan sebagainya. Lapisan kunci tidak selalu diadaptasi, dan idealnya, Anda harus memeriksa apakah menyertakannya menghasilkan kinerja yang lebih baik.

Selain itu, lapisan linear juga merupakan target umum untuk diadaptasi (misalnya, dalam makalah QLoRA, para penulis menyarankan untuk mengadaptasinya juga). Nama-nama lapisan ini sering kali mengandung string fc atau dense.

Jika Anda ingin menambahkan model baru ke PEFT, silakan buat entri di constants.py dan buka pull request di repository. Jangan lupa untuk memperbarui README juga.

Verifikasi parameter dan lapisan

Anda dapat memverifikasi apakah metode PEFT telah diterapkan dengan benar ke model Anda melalui beberapa cara.

  • Jalankan print_healthcheck() untuk memeriksa fraksi parameter yang dapat dilatih dan status adapter. Jika fraksi parameter lebih rendah atau lebih tinggi dari yang diharapkan, periksa repr model dengan mencetak model tersebut. Hal ini menampilkan nama semua jenis layer dalam model. Pastikan hanya layer target yang dimaksud yang diganti oleh layer adapter. Misalnya, jika LoRA diterapkan pada layer nn.Linear, maka Anda seharusnya hanya melihat layer lora.Linear yang digunakan.
peft_model.print_healthcheck()
  • Cara lain untuk melihat lapisan yang diadaptasi adalah dengan menggunakan atribut targeted_module_names untuk mendaftar nama setiap modul yang diadaptasi.
print(peft_model.targeted_module_names)

Tipe modul yang tidak didukung

Metode seperti LoRA hanya berfungsi jika modul target didukung oleh PEFT. Misalnya, Anda dapat menerapkan LoRA pada lapisan nn.Linear dan nn.Conv2d, tetapi tidak, misalnya, pada nn.LSTM. Jika Anda menemukan kelas lapisan yang ingin Anda terapkan PEFT-nya tidak didukung, Anda dapat:

  • mendefinisikan pemetaan kustom untuk mendispatch modul kustom secara dinamis dalam LoRA
  • buka issue dan ajukan fitur tersebut, di mana para maintainer akan mengimplementasikannya atau memandu Anda untuk mengimplementasikannya sendiri jika permintaan untuk tipe modul ini cukup tinggi

Dukungan eksperimental untuk dispatch dinamis modul kustom dalam LoRA

Fitur ini bersifat eksperimental dan dapat berubah, tergantung pada respons komunitas. Kami akan memperkenalkan API publik dan stabil jika terdapat permintaan yang signifikan untuknya.

PEFT mendukung API eksperimental untuk tipe modul kustom untuk LoRA. Asumsikan Anda memiliki implementasi LoRA untuk LSTM. Biasanya, Anda tidak akan bisa menginstruksikan PEFT untuk menggunakannya, meskipun secara teori implementasi tersebut dapat berfungsi dengan PEFT. Namun, hal ini dimungkinkan melalui dispatch dinamis dari lapisan kustom.

API eksperimental saat ini terlihat seperti ini:

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 training

Saat Anda memanggil get_peft_model(), Anda akan melihat peringatan karena PEFT tidak mengenali tipe modul yang ditargetkan. Dalam kasus ini, Anda dapat mengabaikan peringatan tersebut.

Dengan menyediakan pemetaan kustom, PEFT pertama kali memeriksa lapisan model dasar terhadap pemetaan kustom dan mengalokasikan ke tipe lapisan LoRA kustom jika ada kecocokan. Jika tidak ada kecocokan, PEFT memeriksa tipe-tipe lapisan LoRA bawaan untuk mencari kecocokan.

Oleh karena itu, fitur ini juga dapat digunakan untuk menimpa logika dispatch yang sudah ada, misalnya jika Anda ingin menggunakan lapisan LoRA Anda sendiri untuk nn.Linear alih-alih menggunakan yang disediakan oleh PEFT.

Saat membuat modul LoRA kustom Anda, silakan ikuti aturan yang sama seperti modul LoRA yang sudah ada. Beberapa batasan penting yang perlu dipertimbangkan:

  • Modul kustom harus mewarisi dari nn.Module dan peft.tuners.lora.layer.LoraLayer.
  • Metode __init__ dari modul kustom harus memiliki argumen posisional base_layer dan adapter_name. Setelah itu, terdapat **kwargs tambahan yang dapat Anda gunakan atau abaikan.
  • Parameter yang dapat dipelajari harus disimpan dalam nn.ModuleDict atau nn.ParameterDict, di mana kuncinya sesuai dengan nama adapter spesifik (ingat bahwa sebuah model dapat memiliki lebih dari satu adapter pada satu waktu).
  • Nama atribut parameter yang dapat dipelajari ini harus diawali dengan "lora_", misalnya self.lora_new_param = ....
  • Beberapa metode bersifat opsional, misalnya Anda hanya perlu mengimplementasikan merge dan unmerge jika Anda ingin mendukung penggabungan bobot.

Saat ini, informasi tentang modul kustom tidak tersimpan saat Anda menyimpan model. Saat memuat model, Anda harus mendaftarkan ulang modul kustom tersebut.

# 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)

Jika Anda menggunakan fitur ini dan merasa bermanfaat, atau jika Anda menemui masalah, beri tahu kami dengan membuat issue atau diskusi di GitHub. Hal ini memungkinkan kami untuk memperkirakan permintaan terhadap fitur ini dan menambahkan API publik jika permintaannya cukup tinggi.

Update on GitHub