Modernizes ComfyUI-CoreMLSuite onto Python 3.12 / torch 2.7 / coremltools 9 with a characterization-test safety net. The default conversion path is unchanged; existing saved workflows produce identical output. - Toolchain bump (Python 3.12, torch 2.7, coremltools 9, numpy <2) with the blocking upstream pins overridden. - Framework-free logic moved into coreml_suite/core/ (no comfy/coremltools imports); old module paths re-export from there. - Opt-in quantize_nbits dropdown (none|8|6|4) for k-means weight palettization; default none is byte-for-byte identical to before. - Tiered CI: Tier 0 (Linux unit), Tier 1 (macOS-ARM smoke), Tier 2 (self-hosted Apple Silicon golden-image check on the ANE).
198 lines
6.0 KiB
Python
198 lines
6.0 KiB
Python
"""Characterization tests for the .mlpackage filename composition.
|
|
|
|
The filename composition is the pure
|
|
coreml_suite.core.naming.compose_out_name function. CoreMLConverter.convert
|
|
calls it; testing the pure function avoids monkey-patching heavy converter
|
|
internals just to capture the string.
|
|
"""
|
|
import pytest
|
|
|
|
from coreml_suite.core.naming import compose_out_name, lora_names_from_params
|
|
|
|
|
|
# ---------- attention suffixes ----------------------------------------------
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"attn_name,suffix",
|
|
[
|
|
("SPLIT_EINSUM", "se"),
|
|
("SPLIT_EINSUM_V2", "se2"),
|
|
("ORIGINAL", "orig"),
|
|
],
|
|
)
|
|
def test_attention_suffix(attn_name, suffix):
|
|
out = compose_out_name(
|
|
ckpt_name="dreamshaper_8.safetensors",
|
|
batch_size=1, width=512, height=512,
|
|
controlnet_support=False,
|
|
attention_implementation=attn_name,
|
|
)
|
|
assert out == f"dreamshaper_8_1x512x512_{suffix}"
|
|
|
|
|
|
# ---------- batch / size ----------------------------------------------------
|
|
|
|
|
|
def test_includes_batch_and_size():
|
|
out = compose_out_name(
|
|
ckpt_name="dreamshaper_8.safetensors",
|
|
batch_size=4, width=768, height=1024,
|
|
controlnet_support=False,
|
|
attention_implementation="SPLIT_EINSUM",
|
|
)
|
|
assert out == "dreamshaper_8_4x768x1024_se"
|
|
|
|
|
|
# ---------- ControlNet ------------------------------------------------------
|
|
|
|
|
|
def test_appends_cn_suffix_when_controlnet_support_true():
|
|
out = compose_out_name(
|
|
ckpt_name="dreamshaper_8.safetensors",
|
|
batch_size=1, width=512, height=512,
|
|
controlnet_support=True,
|
|
attention_implementation="SPLIT_EINSUM",
|
|
)
|
|
assert out == "dreamshaper_8_1x512x512_cn_se"
|
|
|
|
|
|
# ---------- ckpt name massage -----------------------------------------------
|
|
|
|
|
|
def test_drops_extension_at_first_period():
|
|
out = compose_out_name(
|
|
ckpt_name="my.checkpoint.v2.safetensors",
|
|
batch_size=1, width=512, height=512,
|
|
controlnet_support=False,
|
|
attention_implementation="SPLIT_EINSUM",
|
|
)
|
|
assert out == "my_1x512x512_se"
|
|
|
|
|
|
def test_replaces_spaces_with_underscores():
|
|
out = compose_out_name(
|
|
ckpt_name="dream shaper 8.safetensors",
|
|
batch_size=1, width=512, height=512,
|
|
controlnet_support=False,
|
|
attention_implementation="SPLIT_EINSUM",
|
|
)
|
|
assert out == "dream_shaper_8_1x512x512_se"
|
|
|
|
|
|
# ---------- LoRA suffixes ---------------------------------------------------
|
|
|
|
|
|
def test_single_lora():
|
|
out = compose_out_name(
|
|
ckpt_name="dreamshaper_8.safetensors",
|
|
batch_size=1, width=512, height=512,
|
|
controlnet_support=False,
|
|
attention_implementation="SPLIT_EINSUM",
|
|
lora_names=["epi_noiseoffset.safetensors"],
|
|
)
|
|
assert out == "dreamshaper_8_epi_noiseoffset_1x512x512_se"
|
|
|
|
|
|
def test_multiple_loras_sorted():
|
|
out = compose_out_name(
|
|
ckpt_name="dreamshaper_8.safetensors",
|
|
batch_size=1, width=512, height=512,
|
|
controlnet_support=False,
|
|
attention_implementation="SPLIT_EINSUM",
|
|
lora_names=["zoom.safetensors", "alpha.safetensors", "moody.safetensors"],
|
|
)
|
|
assert out == "dreamshaper_8_alpha_moody_zoom_1x512x512_se"
|
|
|
|
|
|
def test_lora_plus_controlnet():
|
|
out = compose_out_name(
|
|
ckpt_name="dreamshaper_8.safetensors",
|
|
batch_size=1, width=512, height=512,
|
|
controlnet_support=True,
|
|
attention_implementation="SPLIT_EINSUM",
|
|
lora_names=["a.safetensors"],
|
|
)
|
|
assert out == "dreamshaper_8_a_1x512x512_cn_se"
|
|
|
|
|
|
# ---------- sdxl combinations -----------------------------------------------
|
|
|
|
|
|
def test_sdxl_1024_original_gpu():
|
|
out = compose_out_name(
|
|
ckpt_name="sd_xl_base_1.0.safetensors",
|
|
batch_size=1, width=1024, height=1024,
|
|
controlnet_support=False,
|
|
attention_implementation="ORIGINAL",
|
|
)
|
|
assert out == "sd_xl_base_1_1x1024x1024_orig"
|
|
|
|
|
|
# ---------- lora_names_from_params helper ----------------------------------
|
|
|
|
|
|
def test_lora_names_from_params_sorts_by_name():
|
|
names = lora_names_from_params([
|
|
("zebra.safetensors", 1.0),
|
|
("apple.safetensors", 0.5),
|
|
("mango.safetensors", 0.7),
|
|
])
|
|
assert names == ["apple.safetensors", "mango.safetensors", "zebra.safetensors"]
|
|
|
|
|
|
def test_lora_names_from_params_empty_list():
|
|
assert lora_names_from_params([]) == []
|
|
|
|
|
|
# ---------- quantize_nbits suffix ------------------------------------------
|
|
|
|
|
|
def test_quantize_nbits_none_appends_nothing():
|
|
"""'none' is the default and must keep the unquantized filename so
|
|
existing cached .mlpackages still resolve."""
|
|
out = compose_out_name(
|
|
ckpt_name="dreamshaper_8.safetensors",
|
|
batch_size=1, width=512, height=512,
|
|
controlnet_support=False,
|
|
attention_implementation="SPLIT_EINSUM",
|
|
quantize_nbits="none",
|
|
)
|
|
assert out == "dreamshaper_8_1x512x512_se"
|
|
|
|
|
|
@pytest.mark.parametrize("nbits,suffix", [("4", "_q4"), ("6", "_q6"), ("8", "_q8")])
|
|
def test_quantize_nbits_appends_q_suffix(nbits, suffix):
|
|
out = compose_out_name(
|
|
ckpt_name="dreamshaper_8.safetensors",
|
|
batch_size=1, width=512, height=512,
|
|
controlnet_support=False,
|
|
attention_implementation="SPLIT_EINSUM",
|
|
quantize_nbits=nbits,
|
|
)
|
|
assert out == f"dreamshaper_8_1x512x512_se{suffix}"
|
|
|
|
|
|
def test_quantize_nbits_with_controlnet_and_lora():
|
|
out = compose_out_name(
|
|
ckpt_name="dreamshaper_8.safetensors",
|
|
batch_size=1, width=512, height=512,
|
|
controlnet_support=True,
|
|
attention_implementation="SPLIT_EINSUM",
|
|
lora_names=["a.safetensors"],
|
|
quantize_nbits="6",
|
|
)
|
|
assert out == "dreamshaper_8_a_1x512x512_cn_se_q6"
|
|
|
|
|
|
def test_quantize_nbits_invalid_raises():
|
|
import pytest as _pytest
|
|
with _pytest.raises(ValueError, match="quantize_nbits"):
|
|
compose_out_name(
|
|
ckpt_name="x.safetensors",
|
|
batch_size=1, width=512, height=512,
|
|
controlnet_support=False,
|
|
attention_implementation="SPLIT_EINSUM",
|
|
quantize_nbits="16", # not in {none, 8, 6, 4}
|
|
)
|