Phase 3 of the modernization plan: move the framework-free math out of the comfy-coupled modules so Tier-0 tests can run on plain Linux without ComfyUI, coremltools, or python_coreml_stable_diffusion. New pure-core package (no comfy / coreml / mps imports): - coreml_suite.core.latents: chunk_batch, merge_chunks - coreml_suite.core.controlnet: expand_inputs, no_control, extract_residual_kwargs, chunk_control - coreml_suite.core.inputs: CoreMLInputs (chunks + coreml_kwargs) - coreml_suite.core.sdxl: is_sdxl / is_sdxl_base / is_sdxl_refiner, build_sdxl_time_ids (base len 6, refiner len 5), build_sdxl_text_embeds, sdxl_model_function_wrapper - coreml_suite.core.naming: compose_out_name, lora_names_from_params Thin adapters keep the public import paths: - coreml_suite.latents / coreml_suite.controlnet: re-export from core - coreml_suite.models: CoreMLModelWrapper, CoreMLModelWrapperLCM, add_sdxl_model_options (now uses the pure builders from core.sdxl), get_latent_image, get_model_patcher remain framework-coupled - coreml_suite.nodes: CoreMLConverter.convert now delegates the out_name composition to core.naming.compose_out_name Test infra: - tests/unit/* re-pointed at coreml_suite.core.* - test_chunks.py dropped `from comfy.model_management import ...` and the dead `model_config` fixture (Phase 1 left it broken; Phase 3 removes it entirely) - test_characterization_sdxl_options now targets the pure builders directly via inspect.getclosurevars on the wrapper closure - test_characterization_out_name now calls compose_out_name without the heavy CoreMLConverter monkey-patching that Phase 2 needed - tests/unit/test_tier0_purity.py: new gate that fails if comfy / coremltools / etc leak into sys.modules during a pure `-m unit` run (skipped in mixed runs where m2 / integration legitimately import them) - tests/__init__.py + top-level conftest.py + pyproject addopts `--import-mode=importlib --confcutdir=tests` together stop pytest from importing the repo-root `__init__.py` (the ComfyUI custom-node entry pulls in comfy) - tests/conftest.py adds tier-aware collect_ignore so `-m unit` skips tests/m2 + tests/integration at collection time Verification: - `pytest -m unit tests/` → 88 passed in ~2s; deterministic across runs - Tier-0 purity gate confirms no comfy/coreml/etc in sys.modules - m2 golden image (Phase 2 anchor) still hashes identical → refactor produced bit-for-bit unchanged output - `git diff main -- __init__.py coreml_suite/nodes.py` shows zero churn to NODE_CLASS_MAPPINGS keys or INPUT_TYPES field names (public workflow contract intact)
50 lines
1.8 KiB
Python
50 lines
1.8 KiB
Python
"""Pure out_name composition for the Core ML UNet artifact.
|
|
|
|
Extracted from CoreMLConverter.convert in Phase 3 so the filename contract
|
|
can be tested + reused without instantiating the node. The string is the
|
|
cache key: every workflow that references a converted .mlpackage depends
|
|
on it staying byte-for-byte identical.
|
|
"""
|
|
from typing import Iterable, Tuple
|
|
|
|
ATTN_SUFFIX = {
|
|
"SPLIT_EINSUM": "se",
|
|
"SPLIT_EINSUM_V2": "se2",
|
|
"ORIGINAL": "orig",
|
|
}
|
|
|
|
|
|
def compose_out_name(
|
|
*,
|
|
ckpt_name: str,
|
|
batch_size: int,
|
|
width: int,
|
|
height: int,
|
|
controlnet_support: bool,
|
|
attention_implementation: str,
|
|
lora_names: Iterable[str] = (),
|
|
) -> str:
|
|
"""Build the .mlpackage stem from convert() parameters.
|
|
|
|
Locked behaviour (Phase 2 characterization tests):
|
|
- first '.' in ckpt_name wins (`a.b.c.safetensors` -> `a`)
|
|
- spaces collapse to underscores
|
|
- LoRA names are taken stem-only, sorted, joined with '_' and
|
|
prefixed with '_' when present (caller is expected to pass a
|
|
sorted list; we sort defensively)
|
|
- controlnet adds `_cn`
|
|
- attn suffix is `_se` | `_se2` | `_orig`
|
|
"""
|
|
stem = ckpt_name.split(".")[0]
|
|
sorted_names = sorted(lora_names)
|
|
lora_str = "_" + "_".join(name.split(".")[0] for name in sorted_names) if sorted_names else ""
|
|
cn_suffix = "_cn" if controlnet_support else ""
|
|
attn_suffix = "_" + ATTN_SUFFIX[attention_implementation]
|
|
out_name = f"{stem}{lora_str}_{batch_size}x{width}x{height}{cn_suffix}{attn_suffix}"
|
|
return out_name.replace(" ", "_")
|
|
|
|
|
|
def lora_names_from_params(lora_params: Iterable[Tuple[str, float]]) -> list[str]:
|
|
"""Mirror the sort applied inside CoreMLConverter.convert."""
|
|
return [name for name, _ in sorted(lora_params, key=lambda pair: pair[0])]
|