Vlad Iliescu commited on
Commit
4511070
·
1 Parent(s): 015b86f

better lora loading

Browse files
Files changed (2) hide show
  1. lora_utils.py +90 -7
  2. tests/test_lora_utils.py +52 -1
lora_utils.py CHANGED
@@ -135,6 +135,40 @@ def _sorted_lora_entries(entries):
135
  return sorted(entries, key=lambda entry: entry["adapter_name"])
136
 
137
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
138
  def safe_unload_lora_adapters(pipe):
139
  deleted = False
140
  for host in _iter_adapter_hosts(pipe):
@@ -212,9 +246,13 @@ def apply_lora_adapters(pipe, lora_entries):
212
  adapter_names = [entry["adapter_name"] for entry in sorted_entries]
213
  adapter_weights = [entry["scale"] for entry in sorted_entries]
214
 
 
215
  for host in _iter_adapter_hosts(pipe):
216
  if _set_adapters_on_host(host, adapter_names, adapter_weights):
217
- return
 
 
 
218
 
219
  if len(adapter_names) == 1 and hasattr(pipe, "set_lora_scale"):
220
  pipe.set_lora_scale(adapter_weights[0])
@@ -231,13 +269,58 @@ def load_lora_adapter(pipe, entry, token=HF_TOKEN):
231
  if token:
232
  load_kwargs["token"] = token
233
 
234
- try:
235
- pipe.load_lora_weights(entry["repo_id"], **load_kwargs)
236
- except TypeError:
237
- if not token:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
238
  raise
239
- load_kwargs.pop("token", None)
240
- pipe.load_lora_weights(entry["repo_id"], **load_kwargs)
241
 
242
 
243
  def ensure_loras_loaded(pipe, spec_text: str, global_scale: float, active_by_key: dict, token=HF_TOKEN):
 
135
  return sorted(entries, key=lambda entry: entry["adapter_name"])
136
 
137
 
138
+ def _download_lora_weight(repo_id: str, weight_name: str, token=HF_TOKEN):
139
+ from huggingface_hub import hf_hub_download
140
+
141
+ kwargs = {}
142
+ if token:
143
+ kwargs["token"] = token
144
+ return hf_hub_download(repo_id, filename=weight_name, **kwargs)
145
+
146
+
147
+ def _load_adapter_state_dict(local_path: str):
148
+ if local_path.endswith(".safetensors"):
149
+ from safetensors.torch import load_file as safetensors_load_file
150
+
151
+ return safetensors_load_file(local_path)
152
+
153
+ import torch
154
+
155
+ return torch.load(local_path, map_location="cpu")
156
+
157
+
158
+ def _ensure_pipeline_lora_prefix(state_dict):
159
+ if any(key.startswith("transformer.") for key in state_dict.keys()):
160
+ return state_dict
161
+
162
+ if all(
163
+ key.startswith("single_transformer_blocks.")
164
+ or key.startswith("transformer_blocks.")
165
+ for key in state_dict.keys()
166
+ ):
167
+ return {f"transformer.{key}": value for key, value in state_dict.items()}
168
+
169
+ return state_dict
170
+
171
+
172
  def safe_unload_lora_adapters(pipe):
173
  deleted = False
174
  for host in _iter_adapter_hosts(pipe):
 
246
  adapter_names = [entry["adapter_name"] for entry in sorted_entries]
247
  adapter_weights = [entry["scale"] for entry in sorted_entries]
248
 
249
+ activated = False
250
  for host in _iter_adapter_hosts(pipe):
251
  if _set_adapters_on_host(host, adapter_names, adapter_weights):
252
+ activated = True
253
+
254
+ if activated:
255
+ return
256
 
257
  if len(adapter_names) == 1 and hasattr(pipe, "set_lora_scale"):
258
  pipe.set_lora_scale(adapter_weights[0])
 
269
  if token:
270
  load_kwargs["token"] = token
271
 
272
+ native_error = None
273
+ if hasattr(pipe, "load_lora_weights"):
274
+ try:
275
+ pipe.load_lora_weights(entry["repo_id"], **load_kwargs)
276
+ return
277
+ except TypeError as exc:
278
+ native_error = exc
279
+ if token:
280
+ load_kwargs.pop("token", None)
281
+ try:
282
+ pipe.load_lora_weights(entry["repo_id"], **load_kwargs)
283
+ return
284
+ except Exception as retry_exc:
285
+ native_error = retry_exc
286
+ except Exception as exc:
287
+ native_error = exc
288
+
289
+ local_path = _download_lora_weight(entry["repo_id"], entry["weight_name"], token=token)
290
+ state_dict = _load_adapter_state_dict(local_path)
291
+
292
+ loaded = False
293
+ for host in _iter_adapter_hosts(pipe):
294
+ if not hasattr(host, "load_lora_adapter"):
295
+ continue
296
+ try:
297
+ host.load_lora_adapter(dict(state_dict), adapter_name=entry["adapter_name"], prefix=None)
298
+ loaded = True
299
+ continue
300
+ except TypeError:
301
+ try:
302
+ host.load_lora_adapter(dict(state_dict), adapter_name=entry["adapter_name"])
303
+ loaded = True
304
+ continue
305
+ except Exception as exc:
306
+ native_error = native_error or exc
307
+ except Exception as exc:
308
+ native_error = native_error or exc
309
+
310
+ if loaded:
311
+ return
312
+
313
+ if hasattr(pipe, "load_lora_weights"):
314
+ try:
315
+ fallback_kwargs = {"adapter_name": entry["adapter_name"]}
316
+ pipe.load_lora_weights(_ensure_pipeline_lora_prefix(state_dict), **fallback_kwargs)
317
+ return
318
+ except Exception as exc:
319
+ if native_error is not None:
320
+ raise ValueError(f"{native_error}; fallback failed with {exc}") from exc
321
  raise
322
+
323
+ raise ValueError("This pipeline does not expose a LoRA loader on itself or its transformers.")
324
 
325
 
326
  def ensure_loras_loaded(pipe, spec_text: str, global_scale: float, active_by_key: dict, token=HF_TOKEN):
tests/test_lora_utils.py CHANGED
@@ -1,6 +1,33 @@
1
  import unittest
 
2
 
3
- from lora_utils import parse_adapter_specs
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
 
5
 
6
  class ParseAdapterSpecsTest(unittest.TestCase):
@@ -48,6 +75,30 @@ class ParseAdapterSpecsTest(unittest.TestCase):
48
  1.0,
49
  )
50
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
 
52
  if __name__ == "__main__":
53
  unittest.main()
 
1
  import unittest
2
+ from unittest import mock
3
 
4
+ from lora_utils import ensure_loras_loaded, parse_adapter_specs
5
+
6
+
7
+ class DummyAdapterHost:
8
+ def __init__(self):
9
+ self.loaded = []
10
+ self.active_names = None
11
+ self.active_weights = None
12
+
13
+ def load_lora_adapter(self, state_dict, adapter_name, prefix=None):
14
+ self.loaded.append((state_dict, adapter_name, prefix))
15
+
16
+ def set_adapters(self, adapter_names, adapter_weights=None):
17
+ self.active_names = adapter_names
18
+ self.active_weights = adapter_weights
19
+
20
+ def get_list_adapters(self):
21
+ return [adapter_name for _, adapter_name, _ in self.loaded]
22
+
23
+ def delete_adapters(self, adapter_name):
24
+ self.loaded = [entry for entry in self.loaded if entry[1] != adapter_name]
25
+
26
+
27
+ class DummyIdeogramPipeline:
28
+ def __init__(self):
29
+ self.transformer = DummyAdapterHost()
30
+ self.unconditional_transformer = DummyAdapterHost()
31
 
32
 
33
  class ParseAdapterSpecsTest(unittest.TestCase):
 
75
  1.0,
76
  )
77
 
78
+ def test_loads_lora_without_pipeline_loader(self):
79
+ pipe = DummyIdeogramPipeline()
80
+ active_by_key = {}
81
+ state_dict = {"transformer_blocks.0.attn.to_q.lora_A.weight": object()}
82
+
83
+ with mock.patch("lora_utils._download_lora_weight", return_value="/tmp/adapter.safetensors"), mock.patch(
84
+ "lora_utils._load_adapter_state_dict", return_value=state_dict
85
+ ):
86
+ entries = ensure_loras_loaded(
87
+ pipe,
88
+ "vladi/real-loras:ema4_flux2_klein_9b_000002000.safetensors",
89
+ 0.8,
90
+ active_by_key,
91
+ token=None,
92
+ )
93
+
94
+ adapter_name = entries[0]["adapter_name"]
95
+ self.assertEqual(pipe.transformer.loaded[0], (state_dict, adapter_name, None))
96
+ self.assertEqual(pipe.unconditional_transformer.loaded[0], (state_dict, adapter_name, None))
97
+ self.assertEqual(pipe.transformer.active_names, [adapter_name])
98
+ self.assertEqual(pipe.unconditional_transformer.active_names, [adapter_name])
99
+ self.assertEqual(pipe.transformer.active_weights, [0.8])
100
+ self.assertEqual(pipe.unconditional_transformer.active_weights, [0.8])
101
+
102
 
103
  if __name__ == "__main__":
104
  unittest.main()