Support mixed-prefix quantized Consensus merges

This commit is contained in:
silveroxides
2026-09-23 02:42:27 +02:00
parent 1861d75aab
commit 66f795de97
2 changed files with 198 additions and 49 deletions
+119 -44
View File
@@ -39,6 +39,8 @@ from .merger import (
load_documentation_from_file,
)
from .quantization_guard import (
DiffusionQuantization,
diffusion_key_map,
inspect_low_bit_input,
write_preserved_tensor,
)
@@ -1716,14 +1718,31 @@ class ConsensusMergerLogic:
boundary_path, low_memory=params["lazy_load"]
)
try:
low_bit_sets = [
inspect_low_bit_input(
handler,
f"Input {index + 1} ({path})",
"CWB Merge",
)
for index, (handler, path) in enumerate(zip(handlers, paths))
]
diffusion_quantizers = []
diffusion_maps = []
if model_type == "diffusion_models":
diffusion_quantizers = [
DiffusionQuantization(handler, path, f"CWB input {index + 1}")
for index, (handler, path) in enumerate(zip(handlers, paths))
]
diffusion_maps = [
diffusion_key_map(quantizer.data_keys, f"CWB input {index + 1}")
for index, quantizer in enumerate(diffusion_quantizers)
]
low_bit_sets = [
{logical for logical, physical in mapping.items()
if physical in quantizer.isolated_low_bit_keys}
for mapping, quantizer in zip(diffusion_maps, diffusion_quantizers)
]
else:
low_bit_sets = [
inspect_low_bit_input(
handler,
f"Input {index + 1} ({path})",
"CWB Merge",
)
for index, (handler, path) in enumerate(zip(handlers, paths))
]
settings = resolve_cwb_settings(
params["cwb_preset"],
_presets_for(
@@ -1746,6 +1765,8 @@ class ConsensusMergerLogic:
embedding_union=embedding_union,
boundary_handler=boundary_handler,
source_models=model_names,
diffusion_quantizers=diffusion_quantizers,
diffusion_maps=diffusion_maps,
)
if params.get("_return_cwb_report"):
report = diagnostics.render(
@@ -1786,11 +1807,17 @@ class ConsensusMergerLogic:
embedding_union,
boundary_handler=None,
source_models,
diffusion_quantizers,
diffusion_maps,
):
primary = handlers[0]
keys = set()
for handler in handlers:
keys.update(handler.keys())
if diffusion_maps:
for mapping in diffusion_maps:
keys.update(mapping)
else:
for handler in handlers:
keys.update(handler.keys())
keys = sorted(keys)
output_path = cls._output_path(model_type, params["output_filename"])
requested_dtype = _requested_dtype(params["save_dtype"])
@@ -1804,23 +1831,73 @@ class ConsensusMergerLogic:
secondary_only_merged = 0
handler_map = dict(enumerate(handlers))
def source_indices_for(key):
return [
i for i, handler in enumerate(handlers)
if key in (diffusion_maps[i] if diffusion_maps else handler.keys())
]
def physical_key(index, key):
return diffusion_maps[index][key] if diffusion_maps else key
def source_dtype(index, key):
source_key = physical_key(index, key)
return (
diffusion_quantizers[index].logical_dtype(source_key, requested_dtype)
if diffusion_maps else handlers[index].get_dtype(source_key)
)
def matches(key, patterns):
aliases = [key]
if diffusion_maps:
aliases.extend(mapping[key] for mapping in diffusion_maps if key in mapping)
return any(
_matches_any_pattern(alias, patterns, glob_mode=glob_mode)
for alias in aliases
)
work_units = []
for key in keys:
source_indices = [i for i, handler in enumerate(handlers) if key in handler.keys()]
discarded = _matches_any_pattern(key, discard, glob_mode=glob_mode)
source_indices = source_indices_for(key)
discarded = matches(key, discard)
guarded = any(key in low_bit_sets[i] for i in source_indices)
entries = {} if discarded or guarded else {
i: [key] for i in source_indices
}
if discarded:
entries = {}
elif guarded:
preserve_index = 0 if 0 in source_indices else source_indices[0]
source_key = physical_key(preserve_index, key)
entries = (
{preserve_index: diffusion_quantizers[preserve_index].required_keys(source_key)}
if diffusion_maps and source_key in diffusion_quantizers[preserve_index].quantized_keys
else {}
)
else:
entries = {
i: (diffusion_quantizers[i].required_keys(physical_key(i, key))
if diffusion_maps else [key]) for i in source_indices
}
work_units.append((key, entries))
output_metadata = (primary.metadata() or {}).copy()
output_metadata = (
diffusion_quantizers[0].output_metadata()
if diffusion_maps else (primary.metadata() or {}).copy()
)
output_metadata["cwb.merge"] = _cwb_merge_metadata(
source_models,
params,
settings,
operation="cwb_embedding_merge" if embedding_union else "cwb_tensor_merge",
)
def preserve_tensor(writer, index, key, loaded, *, force_raw=False):
source_key = physical_key(index, key)
if diffusion_maps and source_key in diffusion_quantizers[index].quantized_keys:
writer.write(key, loaded[(index, key)].to(requested_dtype).cpu().contiguous())
else:
write_preserved_tensor(
writer, source_key, handlers[index], output_key=key,
force_raw=force_raw, tensor=loaded.get((index, key)),
)
with atomic_uel_writer(output_path, output_metadata) as writer, torch.no_grad(), closing(
stream_work_units(
handler_map, work_units,
@@ -1829,49 +1906,52 @@ class ConsensusMergerLogic:
) as streamed:
for key, loaded in tqdm(streamed, total=len(work_units), desc="CWB merging tensors", unit="tensors"):
_clear_previous_layer(params)
if _matches_any_pattern(key, discard, glob_mode=glob_mode):
if matches(key, discard):
pbar.update(1)
continue
source_indices = [i for i, handler in enumerate(handlers) if key in handler.keys()]
primary_owned = key in primary.keys()
source_indices = source_indices_for(key)
primary_owned = key in (diffusion_maps[0] if diffusion_maps else primary.keys())
secondary_only = not primary_owned
preserve_index = 0 if primary_owned else source_indices[0]
guarded = any(key in low_bit_sets[i] for i in source_indices)
matched = _matches_any_pattern(key, exclude, glob_mode=glob_mode)
if diffusion_maps:
for index in source_indices:
source_key = physical_key(index, key)
if (index, source_key) not in loaded:
continue
if source_key in diffusion_quantizers[index].quantized_keys:
parts = {
part: loaded[(index, part)]
for part in diffusion_quantizers[index].required_keys(source_key)
}
loaded[(index, key)] = diffusion_quantizers[index].decode(
source_key, parts, torch.float32, device="cpu",
)
else:
loaded[(index, key)] = loaded[(index, source_key)]
matched = matches(key, exclude)
excluded = not matched if include_mode else matched
if guarded or excluded:
write_preserved_tensor(
writer,
key,
handlers[preserve_index],
force_raw=guarded,
tensor=loaded.get((preserve_index, key)),
)
preserve_tensor(writer, preserve_index, key, loaded, force_raw=guarded)
if secondary_only:
secondary_only_copied += 1
pbar.update(1)
continue
if secondary_only and len(source_indices) == 1:
write_preserved_tensor(
writer, key, handlers[preserve_index],
tensor=loaded[(preserve_index, key)],
)
preserve_tensor(writer, preserve_index, key, loaded)
secondary_only_copied += 1
pbar.update(1)
continue
source_dtypes = [handlers[i].get_dtype(key) for i in source_indices]
source_dtypes = [source_dtype(i, key) for i in source_indices]
if not all(_is_float_dtype(dtype) for dtype in source_dtypes):
logging.warning(
"[CWB Merge] Preserving non-floating tensor '%s' from input %d.",
key,
preserve_index + 1,
)
write_preserved_tensor(
writer, key, handlers[preserve_index],
tensor=loaded[(preserve_index, key)],
)
preserve_tensor(writer, preserve_index, key, loaded)
if secondary_only:
secondary_only_copied += 1
pbar.update(1)
@@ -1881,9 +1961,7 @@ class ConsensusMergerLogic:
if mismatch_mode == "error":
raise ValueError(f"Tensor '{key}' is missing from a CWB input.")
if mismatch_mode == "skip":
write_preserved_tensor(
writer, key, primary, tensor=loaded[(0, key)]
)
preserve_tensor(writer, 0, key, loaded)
pbar.update(1)
continue
@@ -1972,13 +2050,10 @@ class ConsensusMergerLogic:
if source_index == reference_source:
reference_index = len(tensors)
tensors.append(aligned)
actual_dtypes.append(handler.get_dtype(key))
actual_dtypes.append(source_dtype(source_index, key))
if preserve_for_mismatch:
write_preserved_tensor(
writer, key, handlers[preserve_index],
tensor=loaded[(preserve_index, key)],
)
preserve_tensor(writer, preserve_index, key, loaded)
if secondary_only:
secondary_only_copied += 1
pbar.update(1)
+79 -5
View File
@@ -1,4 +1,5 @@
import importlib
import json
import sys
import types
from pathlib import Path
@@ -1194,6 +1195,78 @@ def test_three_input_streaming_merge(monkeypatch, tmp_path, cwb):
torch.testing.assert_close(tensor, torch.tensor([[3.0, 0.0]]))
@pytest.mark.parametrize("model_count", [2, 3])
@pytest.mark.parametrize("prefixed_input", ["a", "b"])
def test_diffusion_cwb_matches_prefixed_quantized_and_bare_layers(
monkeypatch, tmp_path, cwb, model_count, prefixed_input,
):
names = ["a", "b", "c"][:model_count]
paths = {}
for index, name in enumerate(names):
path = tmp_path / f"mixed_{name}.safetensors"
key = f"{'model.diffusion_model.' if name == prefixed_input else ''}layer.weight"
values = torch.tensor([2.0, 4.0]) * (index + 1)
if name == prefixed_input:
stem = key[:-len(".weight")]
save_file({
key: values.to(torch.int8),
f"{stem}.weight_scale": torch.tensor(1.0),
f"{stem}.comfy_quant": torch.tensor(
list(json.dumps({"format": "int8_tensorwise"}).encode()), dtype=torch.uint8
),
}, str(path))
else:
save_file({key: values}, str(path))
paths[name] = str(path)
_patch_io(monkeypatch, cwb, tmp_path, paths)
result = cwb.ConsensusMergerLogic.execute(
names, "diffusion_models", _params("mixed_cwb")
)
tensors = load_file(str(_result_path(tmp_path, "diffusion_models", result)))
assert list(tensors) == ["layer.weight"]
torch.testing.assert_close(tensors["layer.weight"].float(), torch.tensor([2.0, 4.0]) * (model_count + 1) / 2)
def test_diffusion_cwb_excluded_quantized_layer_is_saved_dense(monkeypatch, tmp_path, cwb):
a = tmp_path / "excluded_quant_a.safetensors"
b = tmp_path / "excluded_quant_b.safetensors"
save_file({
"model.diffusion_model.layer.weight": torch.tensor([1, 2], dtype=torch.int8),
"model.diffusion_model.layer.weight_scale": torch.tensor(2.0),
"model.diffusion_model.layer.comfy_quant": torch.tensor(
list(json.dumps({"format": "int8_tensorwise"}).encode()), dtype=torch.uint8
),
}, str(a))
save_file({"layer.weight": torch.tensor([9.0, 9.0])}, str(b))
_patch_io(monkeypatch, cwb, tmp_path, {"a": str(a), "b": str(b)})
result = cwb.ConsensusMergerLogic.execute(
["a", "b"], "diffusion_models", _params("excluded_quant", include_mode=True),
)
tensors = load_file(str(_result_path(tmp_path, "diffusion_models", result)))
assert list(tensors) == ["layer.weight"]
torch.testing.assert_close(tensors["layer.weight"].float(), torch.tensor([2.0, 4.0]))
def test_diffusion_cwb_guarded_quantized_primary_is_saved_dense(monkeypatch, tmp_path, cwb):
a = tmp_path / "guarded_quant_a.safetensors"
b = tmp_path / "guarded_quant_b.safetensors"
save_file({
"model.diffusion_model.layer.weight": torch.tensor([1, 2], dtype=torch.int8),
"model.diffusion_model.layer.weight_scale": torch.tensor(2.0),
"model.diffusion_model.layer.comfy_quant": torch.tensor(
list(json.dumps({"format": "int8_tensorwise"}).encode()), dtype=torch.uint8
),
}, str(a))
save_file({"layer.weight": torch.tensor([9, 9], dtype=torch.uint8)}, str(b))
_patch_io(monkeypatch, cwb, tmp_path, {"a": str(a), "b": str(b)})
result = cwb.ConsensusMergerLogic.execute(
["a", "b"], "diffusion_models", _params("guarded_quant"),
)
tensors = load_file(str(_result_path(tmp_path, "diffusion_models", result)))
assert list(tensors) == ["layer.weight"]
torch.testing.assert_close(tensors["layer.weight"].float(), torch.tensor([2.0, 4.0]))
def test_two_input_preserves_secondary_only_tensor_exactly(monkeypatch, tmp_path, cwb):
a = tmp_path / "secondary_a.safetensors"
b = tmp_path / "secondary_b.safetensors"
@@ -1289,9 +1362,10 @@ def test_include_filter_preserves_nonmatches_and_discards_first(
lora_mode=lora_mode,
)
tensors = load_file(str(_result_path(tmp_path, category, result)))
assert set(tensors) == {"diffusion_model.selected.diff", "diffusion_model.other.diff"}
torch.testing.assert_close(tensors["diffusion_model.selected.diff"], torch.full((2, 2), 4.0 if selected else 2.0))
torch.testing.assert_close(tensors["diffusion_model.other.diff"], torch.full((2, 2), 2.0))
prefix = "diffusion_model." if lora_mode else ""
assert set(tensors) == {f"{prefix}selected.diff", f"{prefix}other.diff"}
torch.testing.assert_close(tensors[f"{prefix}selected.diff"], torch.full((2, 2), 4.0 if selected else 2.0))
torch.testing.assert_close(tensors[f"{prefix}other.diff"], torch.full((2, 2), 2.0))
def test_secondary_only_filters_and_low_bit_preservation(monkeypatch, tmp_path, cwb):
@@ -1556,7 +1630,7 @@ def test_lora_1d_direct_switch_is_default_fallback_and_fp32(
assert enabled_tensor.dtype == torch.float32
def test_quantized_marker_errors_before_output(monkeypatch, tmp_path, cwb):
def test_invalid_quantization_sidecar_errors_before_output(monkeypatch, tmp_path, cwb):
a = tmp_path / "quant_a.safetensors"
b = tmp_path / "quant_b.safetensors"
save_file({
@@ -1566,7 +1640,7 @@ def test_quantized_marker_errors_before_output(monkeypatch, tmp_path, cwb):
save_file({"layer.weight": torch.ones((2, 2))}, str(b))
paths = {"a": str(a), "b": str(b)}
_patch_io(monkeypatch, cwb, tmp_path, paths)
with pytest.raises(ValueError, match="ComfyUI quantization metadata"):
with pytest.raises(ValueError, match="Invalid quantization sidecar"):
cwb.ConsensusMergerLogic.execute(
["a", "b"], "diffusion_models", _params("must_not_exist")
)