🤗 PEFT 歡迎新的合併方法
模型合併已迅速成為推動大型語言模型效能極限的事實標準。在 Open LLM 排行榜上,我們不斷注意到合併模型位居榜首。我們自己的 Omar Sanseviero 對模型合併進行了一次小小的衝刺,並發現了有趣的發現。
到目前為止,模型合併的典型方式是取一組模型並將其合併。這篇博文提供了關於此主題的很好的入門介紹。通常,對於合併多個模型,我們首先下載它們的檢查點,然後執行合併。根據合併演算法和底層模型的大小,此過程可能非常佔用記憶體。mergekit
庫提供了處理此問題的最佳化方法,使得在有限記憶體下也能管理該過程。
但是,如果我們要合併從*同一個*模型獲得的“不同介面卡”怎麼辦?您可能有從同一個基礎模型獲得的四個不同的 LoRA 檢查點,並且您想嘗試不同的合併技術。最終,您希望選擇最佳合併,為您的任務提供最佳結果。在實現這種開發體驗時,有幾點變得顯而易見
- 在處理 LoRA 等介面卡時,使用者通常會來回切換不同的介面卡,甚至將它們組合起來。介面卡可以被啟用、停用,或完全從記憶體中移除。因此,我們需要即時進行“合併”部分(與上述方法相反),以便為使用者提供無縫體驗。
- 不同的介面卡可能對合並有不同的要求。例如,LoRA 的合併演算法可能無法同樣適用於 IA3。
考慮到這些方面,我們在 🤗 PEFT 中釋出了針對流行 LoRA 介面卡的新合併方法。在這篇博文中,我們想向您介紹可用的方法、幫助您入門的程式碼示例、令人印象深刻的結果以及我們未來的計劃。讓我們開始吧 🚀
目錄
組合/合併 LoRA 介面卡的方法
拼接 (cat
)
在此方法中,LoRA 矩陣被拼接。例如,如果我們有兩個 LoRA 介面卡 和 以及權重 和 用於這兩個介面卡的加權合併,則合併如下所示
其中 。
現在,這個新合併的 LoRA 層的輸出將如同原始的 2 個 LoRA 以權重 和 分別應用於第一個和第二個介面卡時那樣。
這裡,我們可以觀察到
set_adapters()
時,它也透過 Diffusers 的 PEFT 整合提供,其中不是建立新的合併介面卡,而是按順序組合活動介面卡,如上述方程的右側所示。當使用此方法時,它允許參與的 LoRA 介面卡具有不同的秩。
線性/任務算術 (linear
)
在該方法中,LoRA 矩陣進行加權求和。這就是任務算術論文在任務權重上實現的內容。在任務算術中,首先計算任務權重,即微調權重和基礎模型權重之間的差異,然後對這些任務權重進行加權求和。在這裡,所考慮的 delta 權重是單獨的矩陣 和 ,而不是它們的乘積 。此方法僅適用於所有參與的 LoRA 介面卡具有相同秩的情況。
讓我們舉個例子。考慮 2 個 LoRA 介面卡 和 以及權重 和 用於這兩個介面卡的加權合併,則合併如下所示
欲瞭解更多詳情,請參閱論文:使用任務算術編輯模型。
SVD (svd
)
不將單獨的矩陣 和 視為任務權重,而是將其乘積 (即 delta 權重)視為任務權重。
我們繼續使用上一小節的例子。在這裡,首先計算合併組合的 delta 權重,如下所示
在獲得上述合併的 delta 權重後,應用 SVD(奇異值分解)來獲得近似值 和

cat
方法類似,此方法也允許使用不同秩的 LoRA 介面卡。此外,可以為結果合併的 LoRA 介面卡選擇秩,該秩預設為參與 LoRA 介面卡中的最大秩。此方法的一個限制是,執行 SVD 操作需要大量 GPU 記憶體。
TIES (ties
, ties_svd
)
這建立在 linear
和 svd
方法的基礎上,透過改變合併介面卡從任務權重計算的方式,分別產生了 ties
和 ties_svd
方法。在 TIES (TRIM, ELECT SIGN & MERGE) 中,首先計算任務權重,在我們的例子中,對於非 svd 變體將是 LoRA 介面卡 和 ,對於 svd 變體將是它們的乘積 。之後,您根據指定的分數 density
修剪任務權重中最小的值並保留 top-k 值。然後,您從參與的修剪任務權重中計算多數符號掩碼,將任務張量與使用者提供的權重相乘,然後根據多數符號掩碼進行不相交合並。對於多數符號掩碼計算,您有兩個選項
total
同時考慮大小和符號以獲得多數符號,即,將所有相應權重求和;frequency
只考慮權重符號以獲得多數符號,即,將所有相應權重的符號求和。
欲瞭解更多詳情,請參閱論文:TIES-Merging: Resolving Interference When Merging Models。
DARE (dare_linear
, dare_ties
, dare_linear_svd
, dare_ties_svd
)
這也建立在 linear
和 svd
方法的基礎上,其中任務權重是 LoRA 介面卡 和 用於非 SVD 變體,以及它們的乘積 用於 SVD 變體。DARE
方法在 語言模型是超級馬里奧:從同源模型中吸收能力作為免費午餐中提出,首先根據指定的分數 1-density
隨機修剪任務權重的值,然後透過 1/density
重新縮放修剪後的任務權重。DARE
是一個通用的外掛,可以應用於任何現有的模型合併方法。我們已經使用線性/任務算術(*_linear*
)和 TIES(*_ties*
)實現了 DARE
。
對於 DARE
的 *_linear*
變體,我們首先使用 DARE
隨機修剪任務權重,然後根據使用者指定的參與 LoRA 介面卡權重執行任務張量的加權和。
對於 DARE
的 *_ties*
變體,我們首先使用 DARE
獲取修剪後的任務權重,然後採用 ties
的最後 2 個步驟,即計算多數符號掩碼並使用該掩碼執行任務權重的分離合並。
幅度修剪 (magnitude_prune
, magnitude_prune_svd
)
這也建立在 linear
和 svd
方法的基礎上,其中任務權重是 LoRA 介面卡 、(對於非 svd 變體)及其乘積 (對於 svd 變體)。在此方法中,您首先修剪任務權重的最小值,並根據指定的比例 density
保留前 k 個值。然後,您根據使用者指定的參與 LoRA 介面卡的權重執行任務張量的加權和。
如何合併我的 LoRA 介面卡?
在 PEFT 中,使用 LoRA 時,您可以使用類方法 add_weighted_adapter()
嘗試不同的組合方法。例如,下面您可以看到我們如何使用 ties
方法組合三個 LoRA 介面卡,以及新合併介面卡生成的輸出。我們可以觀察到合併後的介面卡能夠保留各個介面卡的能力。
您可以在 PEFT 倉庫的 示例 中找到上述示例。
讓我們再舉一個例子,如下圖所示,使用 magnitude_prune
方法及其生成的輸出。
現在,如果我們要使用合併後的介面卡能力來回答印地語+英語(Hinglish)中與心理健康相關的查詢呢?這將需要使用兩個介面卡的能力。下面我們可以看到查詢“Sad feelings ko kaise dur kare?”(翻譯:如何擺脫悲傷情緒?)的結果。當所有介面卡都停用並使用基礎模型時,響應以“我是 AI”開頭,然後是通用建議。當啟用 Hinglish 介面卡時,響應是 Hinglish 並且簡短,遵循微調資料,但在提供具體的建議以幫助克服悲傷方面做得不好。當啟用 mental_health 介面卡時,響應類似於人類會說的話,但遺憾的是它不是 Hinglish。當啟用合併介面卡時,我們可以看到響應是 Hinglish 並且簡短,同時提供了 mental_health 介面卡響應中可以找到的具體建議,例如鍛鍊、與朋友共度時光、閱讀、冥想和專注於積極思考。因此,我們可以觀察到合併介面卡可以結合它們的個體能力來支援新的用例。
最後,讓我們以 dare_linear
為例,並檢查生成的輸出。
我們在 PEFT 中為這些合併方法提供了專門的開發者指南,您可以在此處找到。
擴充套件到文字到影像生成
在本節中,我們將向您展示如何利用這些合併方法透過 🤗 Diffusers 進行文字到影像生成。請注意,Diffusers 已經依賴 PEFT 進行所有 LoRA 相關操作,包括訓練和推理。但是,目前,在 Diffusers 管道上呼叫 set_adapters()
時,無法利用新的合併方法。這就是為什麼我們正在與社群公開討論如何最好地在 Diffusers 內部原生支援它。
但多虧了 PEFT,總有一種方法可以規避這個問題。我們將為此使用 add_weighted_adapter()
功能。具體來說,我們將採取以下步驟來組合 “toy-face” LoRA 和 “Pixel-Art” LoRA,並嘗試不同的合併技術
- 從這些 LoRA 檢查點獲取
PeftModel
。 - 使用
add_weighted_adapter()
方法和我們選擇的合併方法合併PeftModel
。 - 將合併後的模型分配給底層
DiffusionPipeline
的相應元件。
讓我們看看它的實際應用。下面各部分中顯示的所有程式碼都來自 此 Colab Notebook。
由於這兩個 LoRA 檢查點都使用 SDXL UNet 作為其基礎模型,我們首先載入 UNet
from diffusers import UNet2DConditionModel
import torch
unet = UNet2DConditionModel.from_pretrained(
"stabilityai/stable-diffusion-xl-base-1.0",
torch_dtype=torch.float16,
use_safetensors=True,
variant="fp16",
subfolder="unet",
).to("cuda")
然後我們載入實際的 SDXL 管道和 LoRA 檢查點。我們從“CiroN2022/toy-face” LoRA 開始
from diffusers import DiffusionPipeline
import copy
sdxl_unet = copy.deepcopy(unet)
pipe = DiffusionPipeline.from_pretrained(
"stabilityai/stable-diffusion-xl-base-1.0",
variant="fp16",
torch_dtype=torch.float16,
unet=unet
).to("cuda")
pipe.load_lora_weights("CiroN2022/toy-face", weight_name="toy_face_sdxl.safetensors", adapter_name="toy")
現在,從載入的 LoRA 檢查點獲取 PeftModel
from peft import get_peft_model, LoraConfig
toy_peft_model = get_peft_model(
sdxl_unet,
pipe.unet.peft_config["toy"],
adapter_name="toy"
)
original_state_dict = {f"base_model.model.{k}": v for k, v in pipe.unet.state_dict().items()}
toy_peft_model.load_state_dict(original_state_dict, strict=True)
💡 您可以使用:toy_peft_model.push_to_hub("toy_peft_model", token=TOKEN)
選擇性地將 toy_peft_model
推送到 Hub。
接下來,我們對“nerijs/pixel-art-xl” LoRA 做同樣的事情
pipe.delete_adapters("toy")
sdxl_unet.delete_adapters("toy")
pipe.load_lora_weights("nerijs/pixel-art-xl", weight_name="pixel-art-xl.safetensors", adapter_name="pixel")
pipe.set_adapters(adapter_names="pixel")
pixel_peft_model = get_peft_model(
sdxl_unet,
pipe.unet.peft_config["pixel"],
adapter_name="pixel"
)
original_state_dict = {f"base_model.model.{k}": v for k, v in pipe.unet.state_dict().items()}
pixel_peft_model.load_state_dict(original_state_dict, strict=True)
現在,我們已經具備了加權介面卡推理所需的一切!我們首先載入所有必需的東西
from peft import PeftModel
from diffusers import UNet2DConditionModel, DiffusionPipeline
import torch
base_unet = UNet2DConditionModel.from_pretrained(
"stabilityai/stable-diffusion-xl-base-1.0",
torch_dtype=torch.float16,
use_safetensors=True,
variant="fp16",
subfolder="unet",
).to("cuda")
toy_id = "sayakpaul/toy_peft_model"
model = PeftModel.from_pretrained(base_unet, toy_id, use_safetensors=True, subfolder="toy", adapter_name="toy")
model.load_adapter("sayakpaul/pixel_peft_model", use_safetensors=True, subfolder="pixel", adapter_name="pixel")
現在,組合 LoRA 介面卡——我們都期待的時刻!
model.add_weighted_adapter(
adapters=["toy", "pixel"],
weights=[0.7, 0.3],
combination_type="linear",
adapter_name="toy-pixel"
)
model.set_adapters("toy-pixel")
在這裡,我們只是從“linear”合併策略開始,但將嘗試其他異域合併演算法,例如 TIES。我們最終將 model
分配給我們的 DiffusionPipeline
並執行推理
model = model.to(dtype=torch.float16, device="cuda")
pipe = DiffusionPipeline.from_pretrained(
"stabilityai/stable-diffusion-xl-base-1.0", unet=model, variant="fp16", torch_dtype=torch.float16,
).to("cuda")
prompt = "toy_face of a hacker with a hoodie, pixel art"
image = pipe(prompt, num_inference_steps=30, generator=torch.manual_seed(0)).images[0]
image
讓我們嘗試 ties_svd
方法。您可以在此處找到示例 notebook。
pipe.unet.add_weighted_adapter(
["teapot","watercolour"],
[1.0, 1.0],
"merge",
combination_type="ties_svd",
density=0.5
)
現在,讓我們嘗試使用 dare_linear
組合兩個風格 LoRA
model.add_weighted_adapter(
adapters=["toy", "pixel"],
weights=[1.0, 1.0],
combination_type="dare_linear",
adapter_name="merge",
density=0.7
)
現在,讓我們嘗試使用 majority_sign_method="frequency"
的 ties
方法
model.add_weighted_adapter(
adapters=["toy", "sticker"],
weights=[1.0, 1.0],
combination_type="ties",
adapter_name="merge",
density=0.5,
majority_sign_method="frequency"
)
觀察
- 在大多數情況下,
cat
方法會給出很好的結果。所以,從它開始。但是,請注意,如果您組合許多介面卡,由於連線,生成的合併介面卡可能尺寸很大,導致 OOM。因此,在探索少數介面卡時,cat
將是一個很好的起點。 - 如果您想探索或者
cat
不起作用,請按順序嘗試linear
、maginuted_prune
和dare_linear
。對於maginuted_prune
和dare_linear
,我們發現較高的density
值(約 0.7-0.8)效果更好。 - 在使用
ties
時,我們發現在許多情況下majority_sign_method="frequency"
的表現優於majority_sign_method="total"
(total
是當前的預設值)。對於ties
,density
的一個很好的預設值是 0.5。然後您可以根據合併介面卡後的觀察結果,嘗試將其調低或調高。 dare_ties
沒有給出好結果。- 在使用具有不同等級的 Stable Diffusion LoRA 介面卡時,您可以嘗試
*svd
系列方法。請注意,這些方法需要更多的 GPU 記憶體,並且由於昂貴的 SVD 操作,建立合併介面卡大約需要 1.5 分鐘。ties_svd
在組合subject
+style
LoRA 時給出了很好的結果,如上例所示。在組合 2 個style
介面卡時,dare_linear
具有高density
或ties
具有majority_sign_method="frequency"
似乎效果更好,如上例所示。
致謝
我們非常感謝 DARE 和 TIES 的作者 Le Yu 和 Prateek Yadav 在 PR 中提供的寶貴反饋和指導。為了表彰他們的努力,我們已將他們列為 PR 的共同作者。感謝 Prateek 和 Le 審閱了部落格草稿。
有用連結
- 使用任務算術編輯模型
- TIES-Merging:解決模型合併時的干擾
- 語言模型是超級馬里奧:免費吸收同源模型的能力
- mergekit:用於合併預訓練大型語言模型的工具。
- Diffusers 中的 PEFT 整合
- PEFT 使用者的模型合併指南
引用
@inproceedings{
ilharco2023editing,
title={Editing models with task arithmetic},
author={Gabriel Ilharco and Marco Tulio Ribeiro and Mitchell Wortsman and Ludwig Schmidt and Hannaneh Hajishirzi and Ali Farhadi},
booktitle={The Eleventh International Conference on Learning Representations },
year={2023},
url={https://openreview.net/forum?id=6t0Kwf8-jrj}
}
@inproceedings{
yadav2023tiesmerging,
title={{TIES}-Merging: Resolving Interference When Merging Models},
author={Prateek Yadav and Derek Tam and Leshem Choshen and Colin Raffel and Mohit Bansal},
booktitle={Thirty-seventh Conference on Neural Information Processing Systems},
year={2023},
url={https://openreview.net/forum?id=xtaX3WyCj1}
}
@misc{yu2023language,
title={Language Models are Super Mario: Absorbing Abilities from Homologous Models as a Free Lunch},
author={Le Yu and Bowen Yu and Haiyang Yu and Fei Huang and Yongbin Li},
year={2023},
eprint={2311.03099},
archivePrefix={arXiv},
primaryClass={cs.CL}
}
@misc{
mergekit,
author = {Charles O. Goddard and contributors},
title = {mergekit},
year = {2023},
publisher = {GitHub},
journal = {GitHub repository},
howpublished = {\url{https://github.com/arcee-ai/mergekit}}
}