Skip to content

Commit 499b7d6

Browse files
committed
[FLUX] support LoRA (#9057)
* feat: lora support for Flux. add tests fix imports major fixes. * fix fixes final fixes? * fix * remove is_peft_available.
1 parent 44a4886 commit 499b7d6

File tree

9 files changed

+881
-316
lines changed

9 files changed

+881
-316
lines changed

src/diffusers/loaders/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ def text_encoder_attn_modules(text_encoder):
6666
"SD3LoraLoaderMixin",
6767
"StableDiffusionXLLoraLoaderMixin",
6868
"LoraLoaderMixin",
69+
"FluxLoraLoaderMixin",
6970
]
7071
_import_structure["textual_inversion"] = ["TextualInversionLoaderMixin"]
7172
_import_structure["ip_adapter"] = ["IPAdapterMixin"]
@@ -83,6 +84,7 @@ def text_encoder_attn_modules(text_encoder):
8384
from .ip_adapter import IPAdapterMixin
8485
from .lora_pipeline import (
8586
AmusedLoraLoaderMixin,
87+
FluxLoraLoaderMixin,
8688
LoraLoaderMixin,
8789
SD3LoraLoaderMixin,
8890
StableDiffusionLoraLoaderMixin,

src/diffusers/loaders/lora_pipeline.py

Lines changed: 475 additions & 0 deletions
Large diffs are not rendered by default.

src/diffusers/loaders/peft.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
"UNet2DConditionModel": _maybe_expand_lora_scales,
3333
"UNetMotionModel": _maybe_expand_lora_scales,
3434
"SD3Transformer2DModel": lambda model_cls, weights: weights,
35+
"FluxTransformer2DModel": lambda model_cls, weights: weights,
3536
}
3637

3738

src/diffusers/pipelines/flux/pipeline_flux.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from transformers import CLIPTextModel, CLIPTokenizer, T5EncoderModel, T5TokenizerFast
2121

2222
from ...image_processor import VaeImageProcessor
23-
from ...loaders import SD3LoraLoaderMixin
23+
from ...loaders import FluxLoraLoaderMixin
2424
from ...models.autoencoders import AutoencoderKL
2525
from ...models.transformers import FluxTransformer2DModel
2626
from ...schedulers import FlowMatchEulerDiscreteScheduler
@@ -137,7 +137,7 @@ def retrieve_timesteps(
137137
return timesteps, num_inference_steps
138138

139139

140-
class FluxPipeline(DiffusionPipeline, SD3LoraLoaderMixin):
140+
class FluxPipeline(DiffusionPipeline, FluxLoraLoaderMixin):
141141
r"""
142142
The Flux pipeline for text-to-image generation.
143143
@@ -321,7 +321,7 @@ def encode_prompt(
321321

322322
# set lora scale so that monkey patched LoRA
323323
# function of text encoder can correctly access it
324-
if lora_scale is not None and isinstance(self, SD3LoraLoaderMixin):
324+
if lora_scale is not None and isinstance(self, FluxLoraLoaderMixin):
325325
self._lora_scale = lora_scale
326326

327327
# dynamically adjust the LoRA scale
@@ -354,12 +354,12 @@ def encode_prompt(
354354
)
355355

356356
if self.text_encoder is not None:
357-
if isinstance(self, SD3LoraLoaderMixin) and USE_PEFT_BACKEND:
357+
if isinstance(self, FluxLoraLoaderMixin) and USE_PEFT_BACKEND:
358358
# Retrieve the original scale by scaling back the LoRA layers
359359
unscale_lora_layers(self.text_encoder, lora_scale)
360360

361361
if self.text_encoder_2 is not None:
362-
if isinstance(self, SD3LoraLoaderMixin) and USE_PEFT_BACKEND:
362+
if isinstance(self, FluxLoraLoaderMixin) and USE_PEFT_BACKEND:
363363
# Retrieve the original scale by scaling back the LoRA layers
364364
unscale_lora_layers(self.text_encoder_2, lora_scale)
365365

tests/lora/test_lora_layers_flux.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# coding=utf-8
2+
# Copyright 2024 HuggingFace Inc.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
import sys
16+
import unittest
17+
18+
import torch
19+
from transformers import AutoTokenizer, CLIPTextModel, CLIPTokenizer, T5EncoderModel
20+
21+
from diffusers import FlowMatchEulerDiscreteScheduler, FluxPipeline, FluxTransformer2DModel
22+
from diffusers.utils.testing_utils import floats_tensor, require_peft_backend
23+
24+
25+
sys.path.append(".")
26+
27+
from utils import PeftLoraLoaderMixinTests # noqa: E402
28+
29+
30+
@require_peft_backend
31+
class FluxLoRATests(unittest.TestCase, PeftLoraLoaderMixinTests):
32+
pipeline_class = FluxPipeline
33+
scheduler_cls = FlowMatchEulerDiscreteScheduler()
34+
scheduler_kwargs = {}
35+
uses_flow_matching = True
36+
transformer_kwargs = {
37+
"patch_size": 1,
38+
"in_channels": 4,
39+
"num_layers": 1,
40+
"num_single_layers": 1,
41+
"attention_head_dim": 16,
42+
"num_attention_heads": 2,
43+
"joint_attention_dim": 32,
44+
"pooled_projection_dim": 32,
45+
"axes_dims_rope": [4, 4, 8],
46+
}
47+
transformer_cls = FluxTransformer2DModel
48+
vae_kwargs = {
49+
"sample_size": 32,
50+
"in_channels": 3,
51+
"out_channels": 3,
52+
"block_out_channels": (4,),
53+
"layers_per_block": 1,
54+
"latent_channels": 1,
55+
"norm_num_groups": 1,
56+
"use_quant_conv": False,
57+
"use_post_quant_conv": False,
58+
"shift_factor": 0.0609,
59+
"scaling_factor": 1.5035,
60+
}
61+
has_two_text_encoders = True
62+
tokenizer_cls, tokenizer_id = CLIPTokenizer, "peft-internal-testing/tiny-clip-text-2"
63+
tokenizer_2_cls, tokenizer_2_id = AutoTokenizer, "hf-internal-testing/tiny-random-t5"
64+
text_encoder_cls, text_encoder_id = CLIPTextModel, "peft-internal-testing/tiny-clip-text-2"
65+
text_encoder_2_cls, text_encoder_2_id = T5EncoderModel, "hf-internal-testing/tiny-random-t5"
66+
67+
@property
68+
def output_shape(self):
69+
return (1, 8, 8, 3)
70+
71+
def get_dummy_inputs(self, with_generator=True):
72+
batch_size = 1
73+
sequence_length = 10
74+
num_channels = 4
75+
sizes = (32, 32)
76+
77+
generator = torch.manual_seed(0)
78+
noise = floats_tensor((batch_size, num_channels) + sizes)
79+
input_ids = torch.randint(1, sequence_length, size=(batch_size, sequence_length), generator=generator)
80+
81+
pipeline_inputs = {
82+
"prompt": "A painting of a squirrel eating a burger",
83+
"num_inference_steps": 4,
84+
"guidance_scale": 0.0,
85+
"height": 8,
86+
"width": 8,
87+
"output_type": "np",
88+
}
89+
if with_generator:
90+
pipeline_inputs.update({"generator": generator})
91+
92+
return noise, input_ids, pipeline_inputs

tests/lora/test_lora_layers_sd.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from huggingface_hub import hf_hub_download
2323
from huggingface_hub.repocard import RepoCard
2424
from safetensors.torch import load_file
25+
from transformers import CLIPTextModel, CLIPTokenizer
2526

2627
from diffusers import (
2728
AutoPipelineForImage2Image,
@@ -80,6 +81,12 @@ class StableDiffusionLoRATests(PeftLoraLoaderMixinTests, unittest.TestCase):
8081
"up_block_types": ["UpDecoderBlock2D", "UpDecoderBlock2D"],
8182
"latent_channels": 4,
8283
}
84+
text_encoder_cls, text_encoder_id = CLIPTextModel, "peft-internal-testing/tiny-clip-text-2"
85+
tokenizer_cls, tokenizer_id = CLIPTokenizer, "peft-internal-testing/tiny-clip-text-2"
86+
87+
@property
88+
def output_shape(self):
89+
return (1, 64, 64, 3)
8390

8491
def setUp(self):
8592
super().setUp()

tests/lora/test_lora_layers_sd3.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,9 @@
1515
import sys
1616
import unittest
1717

18-
from diffusers import (
19-
FlowMatchEulerDiscreteScheduler,
20-
StableDiffusion3Pipeline,
21-
)
18+
from transformers import AutoTokenizer, CLIPTextModelWithProjection, CLIPTokenizer, T5EncoderModel
19+
20+
from diffusers import FlowMatchEulerDiscreteScheduler, SD3Transformer2DModel, StableDiffusion3Pipeline
2221
from diffusers.utils.testing_utils import is_peft_available, require_peft_backend, require_torch_gpu, torch_device
2322

2423

@@ -35,6 +34,7 @@ class SD3LoRATests(unittest.TestCase, PeftLoraLoaderMixinTests):
3534
pipeline_class = StableDiffusion3Pipeline
3635
scheduler_cls = FlowMatchEulerDiscreteScheduler()
3736
scheduler_kwargs = {}
37+
uses_flow_matching = True
3838
transformer_kwargs = {
3939
"sample_size": 32,
4040
"patch_size": 1,
@@ -47,6 +47,7 @@ class SD3LoRATests(unittest.TestCase, PeftLoraLoaderMixinTests):
4747
"pooled_projection_dim": 64,
4848
"out_channels": 4,
4949
}
50+
transformer_cls = SD3Transformer2DModel
5051
vae_kwargs = {
5152
"sample_size": 32,
5253
"in_channels": 3,
@@ -61,6 +62,16 @@ class SD3LoRATests(unittest.TestCase, PeftLoraLoaderMixinTests):
6162
"scaling_factor": 1.5035,
6263
}
6364
has_three_text_encoders = True
65+
tokenizer_cls, tokenizer_id = CLIPTokenizer, "hf-internal-testing/tiny-random-clip"
66+
tokenizer_2_cls, tokenizer_2_id = CLIPTokenizer, "hf-internal-testing/tiny-random-clip"
67+
tokenizer_3_cls, tokenizer_3_id = AutoTokenizer, "hf-internal-testing/tiny-random-t5"
68+
text_encoder_cls, text_encoder_id = CLIPTextModelWithProjection, "hf-internal-testing/tiny-sd3-text_encoder"
69+
text_encoder_2_cls, text_encoder_2_id = CLIPTextModelWithProjection, "hf-internal-testing/tiny-sd3-text_encoder-2"
70+
text_encoder_3_cls, text_encoder_3_id = T5EncoderModel, "hf-internal-testing/tiny-random-t5"
71+
72+
@property
73+
def output_shape(self):
74+
return (1, 32, 32, 3)
6475

6576
@require_torch_gpu
6677
def test_sd3_lora(self):

tests/lora/test_lora_layers_sdxl.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import numpy as np
2323
import torch
2424
from packaging import version
25+
from transformers import CLIPTextModel, CLIPTextModelWithProjection, CLIPTokenizer
2526

2627
from diffusers import (
2728
ControlNetModel,
@@ -89,6 +90,14 @@ class StableDiffusionXLLoRATests(PeftLoraLoaderMixinTests, unittest.TestCase):
8990
"latent_channels": 4,
9091
"sample_size": 128,
9192
}
93+
text_encoder_cls, text_encoder_id = CLIPTextModel, "peft-internal-testing/tiny-clip-text-2"
94+
tokenizer_cls, tokenizer_id = CLIPTokenizer, "peft-internal-testing/tiny-clip-text-2"
95+
text_encoder_2_cls, text_encoder_2_id = CLIPTextModelWithProjection, "peft-internal-testing/tiny-clip-text-2"
96+
tokenizer_2_cls, tokenizer_2_id = CLIPTokenizer, "peft-internal-testing/tiny-clip-text-2"
97+
98+
@property
99+
def output_shape(self):
100+
return (1, 64, 64, 3)
92101

93102
def setUp(self):
94103
super().setUp()

0 commit comments

Comments
 (0)