Diffusers 文件

LoRA

Hugging Face's logo
加入 Hugging Face 社群

並獲得增強的文件體驗

開始使用

LoRA

LoRA (低秩適應) 是一種快速訓練模型以適應新任務的方法。它透過凍結原始模型權重並新增少量*新的*可訓練引數來實現。這意味著將現有模型適應新任務(例如以新樣式生成影像)的速度和成本都大大降低。

LoRA 檢查點通常只有幾百 MB 大小,因此它們非常輕便且易於儲存。使用 load_lora_weights() 將這些較小的權重集載入到現有基礎模型中,並指定檔名。

文字到影像
文字到影片
import torch
from diffusers import AutoPipelineForText2Image

pipeline = AutoPipelineForText2Image.from_pretrained(
    "stabilityai/stable-diffusion-xl-base-1.0",
    torch_dtype=torch.float16
).to("cuda")
pipeline.load_lora_weights(
    "ostris/super-cereal-sdxl-lora",
    weight_name="cereal_box_sdxl_v1.safetensors",
    adapter_name="cereal"
)
pipeline("bears, pizza bites").images[0]

load_lora_weights() 方法是載入 LoRA 權重到 UNet 和文字編碼器的首選方法,因為它能夠處理以下情況:

  • LoRA 權重沒有單獨的 UNet 和文字編碼器識別符號
  • LoRA 權重有單獨的 UNet 和文字編碼器識別符號

load_lora_adapter() 方法用於直接在**模型級別**載入 LoRA 介面卡,只要該模型是 `PeftAdapterMixin` 的子類 Diffusers 模型。它會構建並準備介面卡所需的模型配置。此方法還會將 LoRA 介面卡載入到 UNet 中。

例如,如果您只將 LoRA 載入到 UNet 中,load_lora_adapter() 會忽略文字編碼器鍵。使用 `prefix` 引數過濾並載入適當的狀態字典,例如設定為 `"unet"`。

import torch
from diffusers import AutoPipelineForText2Image

pipeline = AutoPipelineForText2Image.from_pretrained(
    "stabilityai/stable-diffusion-xl-base-1.0",
    torch_dtype=torch.float16
).to("cuda")
pipeline.unet.load_lora_adapter(
    "jbilcke-hf/sdxl-cinematic-1",
    weight_name="pytorch_lora_weights.safetensors",
    adapter_name="cinematic"
    prefix="unet"
)
# use cnmt in the prompt to trigger the LoRA
pipeline("A cute cnmt eating a slice of pizza, stunning color scheme, masterpiece, illustration").images[0]

torch.compile

torch.compile 透過編譯 PyTorch 模型以使用最佳化核心來加速推理。在編譯之前,LoRA 權重需要先融合到基礎模型中並解除安裝。

import torch
from diffusers import DiffusionPipeline

# load base model and LoRA
pipeline = DiffusionPipeline.from_pretrained(
    "stabilityai/stable-diffusion-xl-base-1.0",
    torch_dtype=torch.float16
).to("cuda")
pipeline.load_lora_weights(
    "ostris/ikea-instructions-lora-sdxl",
    weight_name="ikea_instructions_xl_v1_5.safetensors",
    adapter_name="ikea"
)

# activate LoRA and set adapter weight
pipeline.set_adapters("ikea", adapter_weights=0.7)

# fuse LoRAs and unload weights
pipeline.fuse_lora(adapter_names=["ikea"], lora_scale=1.0)
pipeline.unload_lora_weights()

通常,UNet 會被編譯,因為它是管道中計算量最大的元件。

pipeline.unet.to(memory_format=torch.channels_last)
pipeline.unet = torch.compile(pipeline.unet, mode="reduce-overhead", fullgraph=True)

pipeline("A bowl of ramen shaped like a cute kawaii bear").images[0]

請參閱熱插拔部分,瞭解如何在處理已編譯模型和多個 LoRA 時避免重新編譯。

權重縮放

`scale` 引數用於控制 LoRA 的應用程度。值為 `0` 等同於僅使用基礎模型權重,值為 `1` 等同於完全使用 LoRA。

簡單用例
更精細的控制

對於簡單用例,您可以將 `cross_attention_kwargs={"scale": 1.0}` 傳遞給管道。

import torch
from diffusers import AutoPipelineForText2Image

pipeline = AutoPipelineForText2Image.from_pretrained(
    "stabilityai/stable-diffusion-xl-base-1.0",
    torch_dtype=torch.float16
).to("cuda")
pipeline.load_lora_weights(
    "ostris/super-cereal-sdxl-lora",
    weight_name="cereal_box_sdxl_v1.safetensors",
    adapter_name="cereal"
)
pipeline("bears, pizza bites", cross_attention_kwargs={"scale": 1.0}).images[0]

縮放排程

在取樣過程中動態調整 LoRA 比例可以更好地控制整體構圖和佈局,因為某些步驟可能更受益於增加或減少的比例。

下面示例中的角色 LoRA 以較高的比例開始,並在前 20 步逐漸衰減以建立角色生成。在後續步驟中,僅應用 0.2 的比例以避免向 LoRA 未訓練的影像其他部分新增過多的 LoRA 特徵。

import torch
from diffusers import FluxPipeline

pipeline = FluxPipeline.from_pretrained(
    "black-forest-labs/FLUX.1-dev", torch_dtype=torch.bfloat16
).to("cuda")

pipelne.load_lora_weights("alvarobartt/ghibli-characters-flux-lora", "lora")

num_inference_steps = 30
lora_steps = 20
lora_scales = torch.linspace(1.5, 0.7, lora_steps).tolist()
lora_scales += [0.2] * (num_inference_steps - lora_steps + 1)

pipeline.set_adapters("lora", lora_scales[0])

def callback(pipeline: FluxPipeline, step: int, timestep: torch.LongTensor, callback_kwargs: dict):
    pipeline.set_adapters("lora", lora_scales[step + 1])
    return callback_kwargs

prompt = """
Ghibli style The Grinch, a mischievous green creature with a sly grin, peeking out from behind a snow-covered tree while plotting his antics, 
in a quaint snowy village decorated for the holidays, warm light glowing from cozy homes, with playful snowflakes dancing in the air
"""
pipeline(
    prompt=prompt,
    guidance_scale=3.0,
    num_inference_steps=num_inference_steps,
    generator=torch.Generator().manual_seed(42),
    callback_on_step_end=callback,
).images[0]

熱插拔

熱插拔 LoRA 是一種高效處理多個 LoRA 的方式,同時避免了多次呼叫 load_lora_weights() 造成的記憶體累積,在某些情況下,如果模型已編譯,還能避免重新編譯。此工作流程需要載入一個 LoRA,因為新的 LoRA 權重會原地替換現有的已載入 LoRA。

import torch
from diffusers import DiffusionPipeline

# load base model and LoRAs
pipeline = DiffusionPipeline.from_pretrained(
    "stabilityai/stable-diffusion-xl-base-1.0",
    torch_dtype=torch.float16
).to("cuda")
pipeline.load_lora_weights(
    "ostris/ikea-instructions-lora-sdxl",
    weight_name="ikea_instructions_xl_v1_5.safetensors",
    adapter_name="ikea"
)

熱插拔不支援以文字編碼器為目標的 LoRA。

load_lora_weights() 中設定 `hotswap=True` 以交換第二個 LoRA。使用 `adapter_name` 引數指示要交換哪個 LoRA (預設名稱為 `default_0`)。

pipeline.load_lora_weights(
    "lordjia/by-feng-zikai",
    hotswap=True,
    adapter_name="ikea"
)

編譯模型

對於已編譯的模型,使用 enable_lora_hotswap() 以避免在熱插拔 LoRA 時重新編譯。此方法應在載入第一個 LoRA *之前* 呼叫,並且 `torch.compile` 應在載入第一個 LoRA *之後* 呼叫。

如果第二個 LoRA 的目標與第一個 LoRA 的 LoRA 等級和比例相同,則 enable_lora_hotswap() 方法並不總是必需的。

enable_lora_hotswap() 中,`target_rank` 引數對於設定所有 LoRA 介面卡的秩非常重要。將其設定為 `max_rank` 會將其設定為最高值。對於具有不同秩的 LoRA,您應將其設定為更高的秩值。預設秩值為 128。

import torch
from diffusers import DiffusionPipeline

# load base model and LoRAs
pipeline = DiffusionPipeline.from_pretrained(
    "stabilityai/stable-diffusion-xl-base-1.0",
    torch_dtype=torch.float16
).to("cuda")
# 1. enable_lora_hotswap
pipeline.enable_lora_hotswap(target_rank=max_rank)
pipeline.load_lora_weights(
    "ostris/ikea-instructions-lora-sdxl",
    weight_name="ikea_instructions_xl_v1_5.safetensors",
    adapter_name="ikea"
)
# 2. torch.compile
pipeline.unet = torch.compile(pipeline.unet, mode="reduce-overhead", fullgraph=True)

# 3. hotswap
pipeline.load_lora_weights(
    "lordjia/by-feng-zikai",
    hotswap=True,
    adapter_name="ikea"
)

將您的程式碼移動到 `with torch._dynamo.config.patch(error_on_recompile=True)` 上下文管理器中,以檢測模型是否已重新編譯。如果模型儘管遵循上述所有步驟仍被重新編譯,請提供可復現的示例並提出issue

在某些情況下,重新編譯仍然不可避免,例如當熱插拔的 LoRA 目標層多於初始介面卡時。嘗試*首先*載入目標層最多的 LoRA。有關此限制的更多詳細資訊,請參閱 PEFT 熱插拔文件。

合併

每個 LoRA 的權重可以合併在一起,以產生多種現有風格的混合。有幾種合併 LoRA 的方法,每種方法在權重合並方式上有所不同(可能會影響生成質量)。

set_adapters

set_adapters() 方法透過連線加權矩陣來合併 LoRA。將 LoRA 名稱傳遞給 set_adapters() 並使用 `adapter_weights` 引數控制每個 LoRA 的縮放。例如,如果 `adapter_weights=[0.5, 0.5]`,則輸出是兩個 LoRA 的平均值。

`"scale"` 引數決定了合併後的 LoRA 應用的程度。有關更多詳細資訊,請參閱權重縮放部分。

import torch
from diffusers import DiffusionPipeline

pipeline = DiffusionPipeline.from_pretrained(
    "stabilityai/stable-diffusion-xl-base-1.0",
    torch_dtype=torch.float16
).to("cuda")
pipeline.load_lora_weights(
    "ostris/ikea-instructions-lora-sdxl",
    weight_name="ikea_instructions_xl_v1_5.safetensors",
    adapter_name="ikea"
)
pipeline.load_lora_weights(
    "lordjia/by-feng-zikai",
    weight_name="fengzikai_v1.0_XL.safetensors",
    adapter_name="feng"
)
pipeline.set_adapters(["ikea", "feng"], adapter_weights=[0.7, 0.8])
# use by Feng Zikai to activate the lordjia/by-feng-zikai LoRA
pipeline("A bowl of ramen shaped like a cute kawaii bear, by Feng Zikai", cross_attention_kwargs={"scale": 1.0}).images[0]

add_weighted_adapter

這是一個實驗性方法,您可以參考 PEFT 的模型合併瞭解更多詳細資訊。如果您對這種整合的動機和設計感興趣,請檢視此問題

`~peft.LoraModel.add_weighted_adapter` 方法支援更高效的合併方法,例如 TIESDARE。這些合併方法會從合併模型中移除冗餘和可能相互干擾的引數。請記住,LoRA 秩需要相同才能合併。

確保安裝了最新穩定版本的 Diffusers 和 PEFT。

pip install -U -q diffusers peft

載入與 LoRA UNet 對應的 UNET。

import copy
import torch
from diffusers import AutoModel, DiffusionPipeline
from peft import get_peft_model, LoraConfig, PeftModel

unet = AutoModel.from_pretrained(
    "stabilityai/stable-diffusion-xl-base-1.0",
    torch_dtype=torch.float16,
    use_safetensors=True,
    variant="fp16",
    subfolder="unet",
).to("cuda")

載入一個管道,將 UNet 傳遞給它,然後載入一個 LoRA。

pipeline = DiffusionPipeline.from_pretrained(
    "stabilityai/stable-diffusion-xl-base-1.0",
    variant="fp16",
    torch_dtype=torch.float16,
    unet=unet
).to("cuda")
pipeline.load_lora_weights(
    "ostris/ikea-instructions-lora-sdxl",
    weight_name="ikea_instructions_xl_v1_5.safetensors",
    adapter_name="ikea"
)

透過組合您載入的第一個 UNet 和管道中的 LoRA UNet,從 LoRA 檢查點建立 `~peft.PeftModel`。

sdxl_unet = copy.deepcopy(unet)
ikea_peft_model = get_peft_model(
    sdxl_unet,
    pipeline.unet.peft_config["ikea"],
    adapter_name="ikea"
)

original_state_dict = {f"base_model.model.{k}": v for k, v in pipeline.unet.state_dict().items()}
ikea_peft_model.load_state_dict(original_state_dict, strict=True)

您可以儲存並重新使用 `ikea_peft_model`,如下所示將其推送到 Hub。

ikea_peft_model.push_to_hub("ikea_peft_model", token=TOKEN)

重複此過程併為第二個 LoRA 建立 `~peft.PeftModel`。

pipeline.delete_adapters("ikea")
sdxl_unet.delete_adapters("ikea")

pipeline.load_lora_weights(
    "lordjia/by-feng-zikai",
    weight_name="fengzikai_v1.0_XL.safetensors",
    adapter_name="feng"
)
pipeline.set_adapters(adapter_names="feng")

feng_peft_model = get_peft_model(
    sdxl_unet,
    pipeline.unet.peft_config["feng"],
    adapter_name="feng"
)

original_state_dict = {f"base_model.model.{k}": v for k, v in pipe.unet.state_dict().items()}
feng_peft_model.load_state_dict(original_state_dict, strict=True)

載入基礎 UNet 模型並載入介面卡。

base_unet = AutoModel.from_pretrained(
    "stabilityai/stable-diffusion-xl-base-1.0",
    torch_dtype=torch.float16,
    use_safetensors=True,
    variant="fp16",
    subfolder="unet",
).to("cuda")

model = PeftModel.from_pretrained(
    base_unet,
    "stevhliu/ikea_peft_model",
    use_safetensors=True,
    subfolder="ikea",
    adapter_name="ikea"
)
model.load_adapter(
    "stevhliu/feng_peft_model",
    use_safetensors=True,
    subfolder="feng",
    adapter_name="feng"
)

使用 `~peft.LoraModel.add_weighted_adapter` 合併 LoRA,並使用 `combination_type` 指定合併方式。以下示例使用 `"dare_linear"` 方法(請參閱此部落格文章瞭解更多關於這些合併方法的資訊),它會隨機剪枝一些權重,然後根據 `weights` 中每個 LoRA 的設定權重對張量執行加權和。

使用 set_adapters() 啟用合併後的 LoRA。

model.add_weighted_adapter(
    adapters=["ikea", "feng"],
    combination_type="dare_linear",
    weights=[1.0, 1.0],
    adapter_name="ikea-feng"
)
model.set_adapters("ikea-feng")

pipeline = DiffusionPipeline.from_pretrained(
    "stabilityai/stable-diffusion-xl-base-1.0",
    unet=model,
    variant="fp16",
    torch_dtype=torch.float16,
).to("cuda")
pipeline("A bowl of ramen shaped like a cute kawaii bear, by Feng Zikai").images[0]

fuse_lora

fuse_lora() 方法將 LoRA 權重直接與底層模型的原始 UNet 和文字編碼器權重融合。這減少了每次 LoRA 載入底層模型的開銷,因為它只加載模型一次,從而降低了記憶體使用並提高了推理速度。

import torch
from diffusers import DiffusionPipeline

pipeline = DiffusionPipeline.from_pretrained(
    "stabilityai/stable-diffusion-xl-base-1.0",
    torch_dtype=torch.float16
).to("cuda")
pipeline.load_lora_weights(
    "ostris/ikea-instructions-lora-sdxl",
    weight_name="ikea_instructions_xl_v1_5.safetensors",
    adapter_name="ikea"
)
pipeline.load_lora_weights(
    "lordjia/by-feng-zikai",
    weight_name="fengzikai_v1.0_XL.safetensors",
    adapter_name="feng"
)
pipeline.set_adapters(["ikea", "feng"], adapter_weights=[0.7, 0.8])

呼叫 fuse_lora() 將它們融合。`lora_scale` 引數控制 LoRA 權重對輸出的縮放程度。現在進行此調整很重要,因為將 `scale` 傳遞給 `cross_attention_kwargs` 在管道中不起作用。

pipeline.fuse_lora(adapter_names=["ikea", "feng"], lora_scale=1.0)

由於 LoRA 權重已與底層模型融合,因此解除安裝它們。使用 save_pretrained() 將融合後的管道儲存到本地,或使用 `~PushToHubMixin.push_to_hub` 將其儲存到 Hub。

本地儲存
儲存到 Hub
pipeline.unload_lora_weights()
pipeline.save_pretrained("path/to/fused-pipeline")

融合後的管道現在可以快速載入進行推理,而無需單獨載入每個 LoRA。

pipeline = DiffusionPipeline.from_pretrained(
    "username/fused-ikea-feng", torch_dtype=torch.float16,
).to("cuda")
pipeline("A bowl of ramen shaped like a cute kawaii bear, by Feng Zikai").images[0]

使用 `unfuse_lora()` 恢復底層模型權重,例如,如果您想使用不同的 `lora_scale` 值。只有在融合了單個 LoRA 時才能取消融合。例如,它不適用於上述管道,因為存在多個融合的 LoRA。在這種情況下,您需要重新載入整個模型。

pipeline.unfuse_lora()

管理

Diffusers 提供了多種方法來幫助您管理 LoRA 的使用。這些方法在您處理多個 LoRA 時特別有用。

set_adapters

set_adapters() 也啟用當前要使用的 LoRA,如果存在多個活動 LoRA。這允許您透過指定其名稱在不同的 LoRA 之間切換。

import torch
from diffusers import DiffusionPipeline

pipeline = DiffusionPipeline.from_pretrained(
    "stabilityai/stable-diffusion-xl-base-1.0",
    torch_dtype=torch.float16
).to("cuda")
pipeline.load_lora_weights(
    "ostris/ikea-instructions-lora-sdxl",
    weight_name="ikea_instructions_xl_v1_5.safetensors",
    adapter_name="ikea"
)
pipeline.load_lora_weights(
    "lordjia/by-feng-zikai",
    weight_name="fengzikai_v1.0_XL.safetensors",
    adapter_name="feng"
)
# activates the feng LoRA instead of the ikea LoRA
pipeline.set_adapters("feng")

save_lora_adapter

使用 save_lora_adapter() 儲存介面卡。

import torch
from diffusers import AutoPipelineForText2Image

pipeline = AutoPipelineForText2Image.from_pretrained(
    "stabilityai/stable-diffusion-xl-base-1.0",
    torch_dtype=torch.float16
).to("cuda")
pipeline.unet.load_lora_adapter(
    "jbilcke-hf/sdxl-cinematic-1",
    weight_name="pytorch_lora_weights.safetensors",
    adapter_name="cinematic"
    prefix="unet"
)
pipeline.save_lora_adapter("path/to/save", adapter_name="cinematic")

unload_lora_weights

unload_lora_weights() 方法會解除安裝管道中的所有 LoRA 權重,以恢復底層模型權重。

pipeline.unload_lora_weights()

disable_lora

disable_lora() 方法會停用所有 LoRA(但它們仍保留在管道中),並將管道恢復為底層模型權重。

pipeline.disable_lora()

get_active_adapters

get_active_adapters() 方法返回附加到管道的活動 LoRA 列表。

pipeline.get_active_adapters()
["cereal", "ikea"]

get_list_adapters

get_list_adapters() 方法返回管道中每個元件的活動 LoRA。

pipeline.get_list_adapters()
{"unet": ["cereal", "ikea"], "text_encoder_2": ["cereal"]}

delete_adapters

delete_adapters() 方法將 LoRA 及其層從模型中完全移除。

pipeline.delete_adapters("ikea")

資源

瀏覽 LoRA Studio 以使用不同的 LoRA,或者您可以透過下面的 Space 將您最喜歡的 Civitai LoRA 上傳到 Hub。

您可以在 FLUX LoRA the ExplorerLoRA the Explorer Space 中找到其他 LoRA。

< > 在 GitHub 上更新

© . This site is unofficial and not affiliated with Hugging Face, Inc.