Files
aszc-dev-ComfyUI-CoreMLSuite/docs/conversion.md
T
aszc-dev fb1b5b3558 docs: sync with converter consolidation
Docs described the pre-#67 suite: a removed standalone LCM converter
node, a removed model_version input, a nonexistent
CoreMLDetailerHookProvider node, and a deleted lcm/converter.py file.

- drop LCM converter node docs; LCM checkpoints are auto-detected by
  the consolidated CoreMLConverter
- remove model_version input and invented 512-768 resolution range
- replace phantom detailer-hook fix with real workarounds
- fix Python support claim (3.12+ per requires-python)
- update LCM support-matrix row, drop stale line reference

Refs #67, #68
2026-11-04 17:51:16 +01:00

94 lines
4.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Conversion
## Conversion is the only supported path
You always start from a Stable Diffusion checkpoint (`.safetensors` / `.ckpt`)
and convert it with the **Convert Checkpoint to Core ML** node. The model
version (SD1.5, SDXL, SDXL refiner, full-distill LCM) is auto-detected from the
checkpoint. Pre-converted Core ML models from elsewhere are not supported,
because:
- The suite uses its own input **dimensions**, **naming convention**, and
**metadata**, all produced by the
[coreml-diffusion](https://github.com/aszc-dev/coreml-diffusion) package.
- Apple's `ml-stable-diffusion` (which most community Core ML models target) is
effectively obsolete, and the layouts differ (see the 2.0.0
`encoder_hidden_states` change in the README).
- Conversion is cheap and one-time, so there is no value in maintaining
backwards compatibility with foreign formats.
The output is always a **`.mlpackage`**. The suite no longer compiles to
`.mlmodelc` (it didn't work with the inference backend), so there is **no Xcode
or `coremlcompiler` dependency**.
## One-time conversion and name-based caching
Conversion runs **once**, not on every queue. The converter encodes all
conversion parameters into the output filename (via `coreml_diffusion.compose_out_name`,
called in `coreml_suite/nodes.py`):
- checkpoint name, `batch_size`, `width`, `height`
- `controlnet_support`, `attention_implementation`
- baked LoRA names, `quantize_nbits`
The result is written as `<encoded-name>_unet.mlpackage` in `models/unet`. If a
file with that name already exists, it is reused and conversion is skipped. Change
any parameter → new name → new conversion; keep them the same → the cached model
is loaded instantly.
This is why the recommended workflow is **convert once, then load**: run the
converter a single time, then in day-to-day use load the `.mlpackage` with the
**Load Core ML UNet** node. (You can also leave the converter node in the graph;
it short-circuits to the cached file.)
> [!NOTE]
> The converter relies on the filename to decide whether to reconvert. If you
> rename the `.mlpackage`, it will be converted again. You can otherwise rename
> it freely if the auto-generated name is too long.
## Quantization
The converter node accepts an optional `quantize_nbits` dropdown that runs
k-means weight palettization (`coremltools.optimize.coreml.palettize_weights`) on
the UNet before saving.
Values: `none` (default — no quantization, identical output and filename to
before), `8`, `6`, `4`. The number is appended to the `.mlpackage` stem as
`_q<bits>`, so quantized and unquantized variants coexist on disk and in cache.
### SD1.5 1×512×512 SPLIT_EINSUM tradeoffs (M2 Pro, ANE)
Measured with 20 UNet forward passes at a fixed seed:
| nbits | size (MB) | size vs none | fwd median (ms) | PSNR vs `none` (dB) |
|---|---:|---:|---:|---:|
| none | 1641 | 1.000 | 197.1 | — |
| 8 | 822 | 0.501 | 186.6 | 53.5 |
| 6 | 617 | 0.376 | 183.0 | 40.2 |
| 4 | 412 | 0.251 | 179.8 | 27.5 |
PSNR here is computed on the raw `noise_pred` output of a single UNet forward at a
fixed seed, not on the final decoded image — it isolates quantization drift from
sampler/VAE noise. Final-image PSNR is comfortably higher (the sampler averages
over many steps).
### Recommended settings per chip / RAM
- **8 GB (M1/M2 base):** `nbits=4`. ~4× smaller, still loads, 27 dB is visually
identical at SD1.5 sizes.
- **16 GB (M1/M2/M3 Pro):** `nbits=6` — the sweet spot, ~2.7× smaller, 40 dB, no
perceptible quality drop.
- **32 GB+ (Max / Ultra):** `nbits=8` for a safety margin, or `none` for
bit-identical output (golden testing).
The default stays `none`, so existing workflows produce byte-for-byte identical
output.
## Where conversion lives
The conversion engine was extracted into the standalone
[coreml-diffusion](https://github.com/aszc-dev/coreml-diffusion) PyPI package.
The nodes in this suite resolve ComfyUI paths and call into it; node names,
inputs, and outputs are unchanged, so the split has effectively no user-facing
impact beyond `pip install` pulling one more dependency.