fix(lora): preserve per-role dtypes

This commit is contained in:
silveroxides
2026-08-13 07:27:27 +02:00
parent 23fa2ad485
commit c84ff102cb
2 changed files with 104 additions and 17 deletions
+43 -17
View File
@@ -108,17 +108,20 @@ def _build_layer_map(lora_infos: List[Dict], base_model_path: Optional[str]) ->
return layer_map
def _logical_layer_dtypes(indices, lora_infos, handlers):
source_dtypes = []
def _logical_output_dtypes(indices, lora_infos, handlers):
pair_dtypes = []
direct_dtypes = {role: [] for role in _DIRECT_MERGE_ROLES}
for idx, orig_block in indices:
block_keys = lora_infos[idx]["pairs"][orig_block]
for name in (
"down", "up", "alpha", "mid", "reshape", "dora_scale",
"diff", "diff_b", "w_norm", "b_norm", "set_weight",
):
for name in ("down", "up"):
if name in block_keys:
source_dtypes.append(handlers[idx].get_dtype(block_keys[name]))
return source_dtypes
pair_dtypes.append(handlers[idx].get_dtype(block_keys[name]))
for role in _DIRECT_MERGE_ROLES:
if role in block_keys:
direct_dtypes[role].append(
handlers[idx].get_dtype(block_keys[role])
)
return pair_dtypes, direct_dtypes
def _merge_direct_weighted(tensors):
@@ -236,14 +239,16 @@ def _build_merge_work_units(
units.append(unit)
continue
pair_dtypes, direct_dtypes = _logical_output_dtypes(
indices, lora_infos, handlers
)
unit = {
"kind": "merge",
"core": core,
"indices": list(indices),
"entries": [],
"source_dtypes": _logical_layer_dtypes(
indices, lora_infos, handlers
),
"pair_source_dtypes": pair_dtypes,
"direct_source_dtypes": direct_dtypes,
}
for idx, block_name in indices:
block_keys = lora_infos[idx]["pairs"][block_name]
@@ -610,7 +615,9 @@ def _process_merge_unit_on_device(
)
output_rank = maximum_rank
layer_dtype = select_output_dtype(unit["source_dtypes"], save_dtype)
layer_dtype = select_output_dtype(
unit["pair_source_dtypes"], save_dtype
)
outputs.extend([
(
canonical_lora_key(unit["core"], "down"),
@@ -655,7 +662,7 @@ def _process_merge_unit_on_device(
generator,
)
direct_dtype = select_output_dtype(
unit["source_dtypes"],
unit["direct_source_dtypes"][role],
save_dtype,
is_1d_diff=merged_direct.ndim == 1,
)
@@ -754,7 +761,8 @@ def _run_multi_lora_merge(
if verbose:
print(
f"[{operation}] LoRA {idx + 1} ({info['name']}): "
f"{len(pairs)} layers, ranks={_format_rank_distribution(ranks)}, "
f"{len(pairs)} target groups, "
f"factor-pair ranks={_format_rank_distribution(ranks)}, "
f"format={info['format']['format']}"
)
@@ -764,6 +772,17 @@ def _run_multi_lora_merge(
units, stream_keys = _build_merge_work_units(
layer_map, lora_infos, handlers, include_1d_diffs
)
if verbose:
coverage = Counter(len(indices) for indices in layer_map.values())
coverage_text = ", ".join(
f"{contributors}-input={count}"
for contributors, count in sorted(coverage.items())
)
print(
f"[{operation}] Merge plan: {len(layer_map)} unique target groups; "
f"contributor coverage: {coverage_text or 'none'}; "
f"execution work units: {len(units)}"
)
cursors = [
_AsyncTensorCursor(
handler,
@@ -792,7 +811,11 @@ def _run_multi_lora_merge(
) as writer:
with torch.no_grad():
for work_index, unit in enumerate(
tqdm(units, desc=f"Merging layers ({strategy})", unit="units")
tqdm(
units,
desc=f"Merging target groups ({strategy})",
unit="units",
)
):
if unit["kind"] == "raw_copy":
for entry in unit["entries"]:
@@ -856,9 +879,12 @@ def _run_multi_lora_merge(
for cursor in cursors:
cursor.finish()
if verbose:
print(f"[{operation}] Output ranks: {_format_rank_distribution(output_ranks)}")
print(
f"[{operation}] Output factor-pair ranks: "
f"{_format_rank_distribution(output_ranks)}"
)
print(f"[{operation}] CUDA OOM CPU fallbacks: {fallback_count}")
print(f"[{operation}] Output state dict has {tensor_count} tensors")
print(f"[{operation}] Output file contains {tensor_count} tensors")
print(f"[{operation}] Saved merged LoRA to {output_path}")
return output_path
finally:
+61
View File
@@ -233,6 +233,67 @@ def test_nonconcatenate_variants_apply_source_alpha_once(
)
@pytest.mark.parametrize(
"strategy", ["concatenate", "weighted_sum", "dare", "enhanced_dare"]
)
def test_fp32_alpha_does_not_promote_bf16_factors(
monkeypatch, tmp_path, merger, strategy
):
_patch_runtime(monkeypatch, tmp_path, merger)
first = tmp_path / f"bf16_alpha_first_{strategy}.safetensors"
second = tmp_path / f"bf16_alpha_second_{strategy}.safetensors"
tensors = {
"diffusion_model.layer.lora_A.weight": torch.ones(
(1, 2), dtype=torch.bfloat16
),
"diffusion_model.layer.lora_B.weight": torch.ones(
(2, 1), dtype=torch.bfloat16
),
"diffusion_model.layer.alpha": torch.tensor(1.0, dtype=torch.float32),
}
_write_uel(first, tensors)
_write_uel(second, tensors)
common = [str(first), str(second)], [1.0, 1.0]
if strategy in {"concatenate", "weighted_sum"}:
output = merger.merge_multi_loras(
*common,
strategy,
"cpu",
torch.bfloat16,
f"bf16_alpha_{strategy}",
verbose=False,
)
elif strategy == "dare":
output = merger.merge_multi_loras_dare(
*common,
0.0,
0.0,
17,
"cpu",
torch.bfloat16,
"bf16_alpha_dare",
verbose=False,
)
else:
output = merger.merge_multi_loras_dare_enhanced(
*common,
1.0,
1.0,
0.0,
0.0,
17,
"cpu",
torch.bfloat16,
"bf16_alpha_enhanced",
verbose=False,
)
merged = _read_uel(output)
assert merged["diffusion_model.layer.lora_A.weight"].dtype == torch.bfloat16
assert merged["diffusion_model.layer.lora_B.weight"].dtype == torch.bfloat16
def test_all_normal_inputs_use_async_uel_and_release_once(
monkeypatch, tmp_path, merger
):