Skip to content

Commit 5d2aa4e

Browse files
committed
lora extract: actually functional for modern models now
1 parent 49d3bc3 commit 5d2aa4e

2 files changed

Lines changed: 39 additions & 57 deletions

File tree

src/BuiltinExtensions/ComfyUIBackend/ComfyUIAPIAbstractBackend.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ await socket.SendJson(new JObject()
303303
float curPercent = 0;
304304
void yieldProgressUpdate()
305305
{
306-
Logs.Verbose($"Progress [{batchId}]: {nodesDone}/{expectedNodes}, curPercent={curPercent:00.00}");
306+
Logs.Verbose($"Progress [{batchId}]: {nodesDone}/{expectedNodes}, curPercent={curPercent * 100:00.0}");
307307
JObject toSend = new()
308308
{
309309
["batch_index"] = batchId,

src/BuiltinExtensions/ComfyUIBackend/ExtraNodes/SwarmComfyCommon/SwarmExtractLora.py

Lines changed: 38 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ def extract_lora(diff, rank):
1818
else:
1919
diff = diff.squeeze()
2020

21-
U, S, Vh = torch.linalg.svd(diff.float())
21+
U, S, Vh = torch.linalg.svd(diff.float(), full_matrices=False)
2222
U = U[:, :rank]
2323
S = S[:rank]
2424
U = U @ torch.diag(S)
@@ -36,65 +36,61 @@ def extract_lora(diff, rank):
3636
return (U, Vh)
3737

3838

39-
def do_lora_handle(base_data, other_data, rank, prefix, require, do_bias, callback):
39+
def do_lora_handle(base_data, other_data, rank, callback):
4040
out_data = {}
4141
device = comfy.model_management.get_torch_device()
4242
for key in base_data.keys():
4343
callback()
4444
if key not in other_data:
4545
continue
46-
base_tensor = base_data[key].float()
47-
other_tensor = other_data[key].float()
48-
if key.startswith("clip_g"):
49-
key = "1." + key[len("clip_g."):]
50-
elif key.startswith("clip_l"):
51-
key = "0." + key[len("clip_l."):]
52-
if require:
53-
if not key.startswith(require):
54-
print(f"Ignore unmatched key {key} (doesn't match {require})")
55-
continue
56-
key = key[len(require):]
46+
if key.endswith(".weight_scale") or key.endswith(".comfy_quant"):
47+
continue
48+
base_tensor = base_data[key]
49+
other_tensor = other_data[key]
50+
if key.endswith(".weight"):
51+
fixed_key = key[:-len(".weight")]
52+
scale_key = f"{fixed_key}.weight_scale"
53+
if scale_key in base_data:
54+
scale = base_data[scale_key]
55+
base_tensor = base_tensor.to(dtype=torch.bfloat16) * scale
56+
if scale_key in other_data:
57+
scale = other_data[scale_key]
58+
other_tensor = other_tensor.to(dtype=torch.bfloat16) * scale
59+
elif key.endswith(".bias") or key.endswith(".scale") or key.endswith(".lin"):
60+
fixed_key = key
5761
if base_tensor.shape != other_tensor.shape:
5862
continue
5963
target_dtype = base_tensor.dtype
6064
if target_dtype == torch.float8_e4m3fn or target_dtype == torch.float8_e5m2:
6165
target_dtype = torch.bfloat16
6266
base_tensor = base_tensor.to(dtype=target_dtype)
6367
other_tensor = other_tensor.to(dtype=target_dtype)
64-
diff = other_tensor.to(device) - base_tensor.to(device)
68+
diff = other_tensor.to(device, dtype=torch.float32) - base_tensor.to(device, dtype=torch.float32)
6569
other_tensor = other_tensor.cpu()
6670
base_tensor = base_tensor.cpu()
6771
max_diff = float(diff.abs().max())
6872
if max_diff < 1e-5:
6973
print(f"discard unaltered key {key} ({max_diff})")
7074
continue
71-
if key.endswith(".weight"):
72-
fixed_key = key[:-len(".weight")].replace('.', '_')
73-
name = f"lora_{prefix}_{fixed_key}"
74-
if len(base_tensor.shape) >= 2:
75-
print(f"extract key {name} ({max_diff})")
76-
out = extract_lora(diff, rank)
77-
up = out[0].contiguous().to(dtype=target_dtype).cpu()
78-
down = out[1].contiguous().to(dtype=target_dtype).cpu()
79-
if up.isnan().any() or up.isinf().any():
80-
print(f"bad data for {name}.lora_up.weight")
81-
continue
82-
if down.isnan().any() or down.isinf().any():
83-
print(f"bad data for {name}.lora_down.weight")
84-
continue
85-
out_data[f"{name}.lora_up.weight"] = up
86-
out_data[f"{name}.lora_down.weight"] = down
87-
else:
88-
print(f"ignore valid raw pass-through key {name} ({max_diff})")
89-
elif key.endswith(".bias") and do_bias:
90-
fixed_key = key[:-len(".bias")].replace('.', '_')
91-
name = f"lora_{prefix}_{fixed_key}"
92-
print(f"extract bias key {name} ({max_diff})")
93-
diff = diff.contiguous().to(dtype=target_dtype).cpu()
94-
if diff.isnan().any() or diff.isinf().any():
95-
print(f"bad data for {name}.diff_b")
75+
if len(base_tensor.shape) >= 2 and base_tensor.numel() < 1024:
76+
print(f"extract key {fixed_key} ({max_diff})")
77+
out = extract_lora(diff, rank)
78+
up = out[0].contiguous().to(dtype=target_dtype).cpu()
79+
down = out[1].contiguous().to(dtype=target_dtype).cpu()
80+
if up.isnan().any() or up.isinf().any():
81+
print(f"bad data for {fixed_key}.lora_up.weight")
82+
continue
83+
if down.isnan().any() or down.isinf().any():
84+
print(f"bad data for {fixed_key}.lora_down.weight")
85+
continue
86+
out_data[f"{fixed_key}.lora_up.weight"] = up
87+
out_data[f"{fixed_key}.lora_down.weight"] = down
88+
else:
89+
out = diff.contiguous().to(dtype=target_dtype).cpu()
90+
if out.isnan().any() or out.isinf().any():
91+
print(f"bad data for {fixed_key}")
9692
continue
97-
out_data[f"{name}.diff_b"] = diff
93+
out_data[f"{fixed_key}.diff"] = out
9894

9995

10096
return out_data
@@ -108,13 +104,10 @@ def INPUT_TYPES(s):
108104
return {
109105
"required": {
110106
"base_model": ("MODEL", ),
111-
"base_model_clip": ("CLIP", ),
112107
"other_model": ("MODEL", ),
113-
"other_model_clip": ("CLIP", ),
114108
"rank": ("INT", {"default": 16, "min": 1, "max": 320}),
115109
"save_rawpath": ("STRING", {"multiline": False}),
116110
"save_filename": ("STRING", {"multiline": False}),
117-
"save_clip": ("BOOLEAN", {"default": True}),
118111
"metadata": ("STRING", {"multiline": True}),
119112
}
120113
}
@@ -125,29 +118,18 @@ def INPUT_TYPES(s):
125118
OUTPUT_NODE = True
126119
DESCRIPTION = "Internal node, do not use directly - extracts a LoRA from the difference between two models. This is used by SwarmUI Utilities tab."
127120

128-
def extract_lora(self, base_model, base_model_clip, other_model, other_model_clip, rank, save_rawpath, save_filename, save_clip, metadata):
121+
def extract_lora(self, base_model, other_model, rank, save_rawpath, save_filename, metadata):
129122
base_data = base_model.model_state_dict()
130123
other_data = other_model.model_state_dict()
131124
key_count = len(base_data.keys())
132-
if save_clip:
133-
if base_model_clip is None or other_model_clip is None:
134-
print("Warning: save_clip is True but CLIP model(s) are unavailable (model may not have embedded CLIP, e.g. Flux), skipping CLIP extraction")
135-
save_clip = False
136-
else:
137-
key_count += len(base_model_clip.get_sd().keys())
138125
pbar = comfy.utils.ProgressBar(key_count)
139126
class Helper:
140127
steps = 0
141128
def callback(self):
142129
self.steps += 1
143130
pbar.update_absolute(self.steps, key_count, None)
144131
helper = Helper()
145-
out_data = do_lora_handle(base_data, other_data, rank, "unet", "diffusion_model.", False, lambda: helper.callback())
146-
if save_clip:
147-
# TODO: CLIP keys get wonky, this probably doesn't work? Model-arch-dependent.
148-
out_clip = do_lora_handle(base_model_clip.get_sd(), other_model_clip.get_sd(), rank, "te_text_model_encoder_layers", "0.transformer.text_model.encoder.layers.", False, lambda: helper.callback())
149-
out_clip = do_lora_handle(base_model_clip.get_sd(), other_model_clip.get_sd(), rank, "te2_text_model_encoder_layers", "1.transformer.text_model.encoder.layers.", False, lambda: helper.callback())
150-
out_data.update(out_clip)
132+
out_data = do_lora_handle(base_data, other_data, rank, lambda: helper.callback())
151133

152134
# Can't easily autodetect all the correct modelspec info, but at least supply some basics
153135
out_metadata = {

0 commit comments

Comments
 (0)