Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit a9354e6

Browse files
authored
Merge pull request #172 from cloneofsimo/feat/mutilora-context-manager
LoRA Manager for managing multiple loras with different scaling parameters & tokens
2 parents 6b82538 + 7be7bed commit a9354e6

4 files changed

Lines changed: 136 additions & 52 deletions

File tree

example_loras/and.safetensors

11.8 MB
Binary file not shown.

lora_diffusion/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
from .dataset import *
33
from .utils import *
44
from .preprocess_files import *
5+
from .lora_manager import *

lora_diffusion/cli_lora_add.py

Lines changed: 1 addition & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
collapse_lora,
1313
monkeypatch_remove_lora,
1414
)
15+
from .lora_manager import lora_join
1516
from .to_ckpt_v2 import convert_to_ckpt
1617

1718

@@ -20,58 +21,6 @@ def _text_lora_path(path: str) -> str:
2021
return ".".join(path.split(".")[:-1] + ["text_encoder", "pt"])
2122

2223

23-
def lora_join(lora_safetenors: list):
24-
metadatas = [dict(safelora.metadata()) for safelora in lora_safetenors]
25-
total_metadata = {}
26-
total_tensor = {}
27-
total_rank = 0
28-
ranklist = []
29-
for _metadata in metadatas:
30-
rankset = []
31-
for k, v in _metadata.items():
32-
if k.endswith("rank"):
33-
rankset.append(int(v))
34-
35-
assert len(set(rankset)) == 1, "Rank should be the same per model"
36-
total_rank += rankset[0]
37-
total_metadata.update(_metadata)
38-
ranklist.append(rankset[0])
39-
40-
tensorkeys = set()
41-
for safelora in lora_safetenors:
42-
tensorkeys.update(safelora.keys())
43-
44-
for keys in tensorkeys:
45-
if keys.startswith("text_encoder") or keys.startswith("unet"):
46-
tensorset = [safelora.get_tensor(keys) for safelora in lora_safetenors]
47-
48-
is_down = keys.endswith("down")
49-
50-
if is_down:
51-
_tensor = torch.cat(tensorset, dim=0)
52-
assert _tensor.shape[0] == total_rank
53-
else:
54-
_tensor = torch.cat(tensorset, dim=1)
55-
assert _tensor.shape[1] == total_rank
56-
57-
total_tensor[keys] = _tensor
58-
keys_rank = ":".join(keys.split(":")[:-1]) + ":rank"
59-
total_metadata[keys_rank] = str(total_rank)
60-
token_size_list = []
61-
for idx, safelora in enumerate(lora_safetenors):
62-
tokens = [k for k, v in safelora.metadata().items() if v == "<embed>"]
63-
for jdx, token in enumerate(sorted(tokens)):
64-
if total_metadata.get(token, None) is not None:
65-
del total_metadata[token]
66-
total_tensor[f"<s{idx}-{jdx}>"] = safelora.get_tensor(token)
67-
total_metadata[f"<s{idx}-{jdx}>"] = "<embed>"
68-
print(f"Embedding {token} replaced to <s{idx}-{jdx}>")
69-
70-
token_size_list.append(len(tokens))
71-
72-
return total_tensor, total_metadata, ranklist, token_size_list
73-
74-
7524
def add(
7625
path_1: str,
7726
path_2: str,

lora_diffusion/lora_manager.py

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
from typing import List
2+
import torch
3+
from safetensors import safe_open
4+
from diffusers import StableDiffusionPipeline
5+
from .lora import (
6+
monkeypatch_or_replace_safeloras,
7+
apply_learned_embed_in_clip,
8+
set_lora_diag,
9+
parse_safeloras_embeds,
10+
)
11+
12+
13+
def lora_join(lora_safetenors: list):
14+
metadatas = [dict(safelora.metadata()) for safelora in lora_safetenors]
15+
total_metadata = {}
16+
total_tensor = {}
17+
total_rank = 0
18+
ranklist = []
19+
for _metadata in metadatas:
20+
rankset = []
21+
for k, v in _metadata.items():
22+
if k.endswith("rank"):
23+
rankset.append(int(v))
24+
25+
assert len(set(rankset)) == 1, "Rank should be the same per model"
26+
total_rank += rankset[0]
27+
total_metadata.update(_metadata)
28+
ranklist.append(rankset[0])
29+
30+
tensorkeys = set()
31+
for safelora in lora_safetenors:
32+
tensorkeys.update(safelora.keys())
33+
34+
for keys in tensorkeys:
35+
if keys.startswith("text_encoder") or keys.startswith("unet"):
36+
tensorset = [safelora.get_tensor(keys) for safelora in lora_safetenors]
37+
38+
is_down = keys.endswith("down")
39+
40+
if is_down:
41+
_tensor = torch.cat(tensorset, dim=0)
42+
assert _tensor.shape[0] == total_rank
43+
else:
44+
_tensor = torch.cat(tensorset, dim=1)
45+
assert _tensor.shape[1] == total_rank
46+
47+
total_tensor[keys] = _tensor
48+
keys_rank = ":".join(keys.split(":")[:-1]) + ":rank"
49+
total_metadata[keys_rank] = str(total_rank)
50+
token_size_list = []
51+
for idx, safelora in enumerate(lora_safetenors):
52+
tokens = [k for k, v in safelora.metadata().items() if v == "<embed>"]
53+
for jdx, token in enumerate(sorted(tokens)):
54+
55+
total_tensor[f"<s{idx}-{jdx}>"] = safelora.get_tensor(token)
56+
total_metadata[f"<s{idx}-{jdx}>"] = "<embed>"
57+
58+
print(f"Embedding {token} replaced to <s{idx}-{jdx}>")
59+
60+
if total_metadata.get(token, None) is not None:
61+
del total_metadata[token]
62+
63+
token_size_list.append(len(tokens))
64+
65+
return total_tensor, total_metadata, ranklist, token_size_list
66+
67+
68+
class DummySafeTensorObject:
69+
def __init__(self, tensor: dict, metadata):
70+
self.tensor = tensor
71+
self._metadata = metadata
72+
73+
def keys(self):
74+
return self.tensor.keys()
75+
76+
def metadata(self):
77+
return self._metadata
78+
79+
def get_tensor(self, key):
80+
return self.tensor[key]
81+
82+
83+
class LoRAManager:
84+
def __init__(self, lora_paths_list: List[str], pipe: StableDiffusionPipeline):
85+
86+
self.lora_paths_list = lora_paths_list
87+
self.pipe = pipe
88+
self._setup()
89+
90+
def _setup(self):
91+
92+
self._lora_safetenors = [
93+
safe_open(path, framework="pt", device="cpu")
94+
for path in self.lora_paths_list
95+
]
96+
97+
(
98+
total_tensor,
99+
total_metadata,
100+
self.ranklist,
101+
self.token_size_list,
102+
) = lora_join(self._lora_safetenors)
103+
104+
self.total_safelora = DummySafeTensorObject(total_tensor, total_metadata)
105+
106+
monkeypatch_or_replace_safeloras(self.pipe, self.total_safelora)
107+
tok_dict = parse_safeloras_embeds(self.total_safelora)
108+
109+
apply_learned_embed_in_clip(
110+
tok_dict,
111+
self.pipe.text_encoder,
112+
self.pipe.tokenizer,
113+
token=None,
114+
idempotent=True,
115+
)
116+
117+
def tune(self, scales):
118+
119+
diags = []
120+
for scale, rank in zip(scales, self.ranklist):
121+
diags = diags + [scale] * rank
122+
123+
set_lora_diag(self.pipe.unet, torch.tensor(diags))
124+
125+
def prompt(self, prompt):
126+
if prompt is not None:
127+
for idx, tok_size in enumerate(self.token_size_list):
128+
prompt = prompt.replace(
129+
f"<{idx + 1}>",
130+
"".join([f"<s{idx}-{jdx}>" for jdx in range(tok_size)]),
131+
)
132+
# TODO : Rescale LoRA + Text inputs based on prompt scale params
133+
134+
return prompt

0 commit comments

Comments
 (0)