← All courses ← Course home

Lesson 04 of 08

Merge, then export

Bake the adapter into the base so Core AI sees one ordinary diffusion graph. Bake means merge the LoRA into the base and export one model file. Then export a portable resource folder.

Agent brief (llms.md)

Why you merge before you convert

Core AI converts exported PyTorch graphs. A LoRA is extra math beside a frozen weight. Unless Apple documents a runtime adapter API, that extra math is not a Core AI feature. Merge so the personalized weights are stored in the UNet. Export that ordinary graph. The model file is stored on the phone.

Keep the unmerged adapter. Merge is not reversible from the baked file. The adapter is how you retrain or rebuild.

The steps, one box at a time

Reload the exact base revision and the chosen LoRA.

InputsBase plus LoRA

Fuse the adapter into the target component. After this step the pipeline should run without PEFT.

InputsBase plus LoRA
BakeMerged UNet

Confirm holdout prompts still match the adapter-attached previews. Then export.

InputsBase plus LoRA
BakeMerged UNet
GraphOrdinary pipeline

The export tool writes a multi-component Core AI resource folder. That folder is what Xcode will load.

InputsBase plus LoRA
BakeMerged UNet
GraphOrdinary pipeline
Export.aimodel resources

Environment setup

Use the same uv project. You need the training stack plus the official export entry points from apple/coreai-models and coreai-torch.

curl -LsSf https://astral.sh/uv/install.sh | sh
mkdir -p diffusion-lora-coreai && cd diffusion-lora-coreai
uv init --python 3.11
uv venv
uv add torch torchvision diffusers peft transformers accelerate datasets safetensors coreai-torch
uv run python -c "import torch; print(torch.__version__); print('MPS:', torch.backends.mps.is_available())"
uv run coreai.diffusion.export --help

Worked path: merge

Save as merge_lora.py. Merge on CPU so the bake is not tied to MPS. Keep the original adapter directory untouched.

from pathlib import Path
from diffusers import StableDiffusionPipeline
from peft import PeftModel
import torch

BASE = "runwayml/stable-diffusion-v1-5"
REVISION = "pin-this"
ADAPTER = "artifacts/style-lora"
MERGED = Path("artifacts/merged-pipeline")

pipe = StableDiffusionPipeline.from_pretrained(BASE, revision=REVISION, torch_dtype=torch.float32)
pipe.unet = PeftModel.from_pretrained(pipe.unet, ADAPTER)
pipe.unet = pipe.unet.merge_and_unload()
pipe.save_pretrained(MERGED)
print("merged into", MERGED)
uv run python merge_lora.py
# Compare one holdout prompt against artifacts/holdout-lora.png before you export.

Worked path: official diffusion export

The CLI takes a positional model. That can be a registry short-name, a Hugging Face id, or the local merged folder if the installed recipe accepts a path. Confirm with --help and --dry-run. Do not invent a --model flag.

uv run coreai.diffusion.export --help
uv run coreai.diffusion.export sd-1.5 --dry-run
# After merge, point the installed recipe at the ordinary merged pipeline.
uv run coreai.diffusion.export artifacts/merged-pipeline \
  --output-dir artifacts/personalized \
  --overwrite
# Official short-name form, once the bake is represented the recipe expects:
# uv run coreai.diffusion.export sd-1.5 --output-dir artifacts/personalized --overwrite

Export every component the pipeline needs. For SD 1.x the documented set is text_encoder, unet, vae_decoder, and vae_encoder. A single guessed file is not a diffusion resource.

Worked path: torch.export plus coreai-torch

Use this when you are converting one exported graph the way the clothing-classifier course does. Diffusion still has several graphs. You will repeat the pattern per component the recipe names.

import torch
from coreai_torch import TorchConverter, get_decomp_table

model = load_merged_component().eval()
example = (cpu_example_tensor,)
ep = torch.export.export(model, args=example)
ep = ep.run_decompositions(get_decomp_table())
program = TorchConverter().add_exported_program(ep).to_coreai()
program.optimize()
program.save("artifacts/personalized/unet.aimodel")
No hot-swap claim. Apple documents base-model export through diffusion recipes or graph conversion. The safe statement is train locally, merge or fuse, export the merged graph, deploy that file.

Failure modes

Done when

Keep learning

PyTorch to Core AI in Xcode · From the metal to the model · Core AI models, typed · Model architectures in plain English · LLM LoRA for your writing style

Key concepts

  • Bake means merge_and_unload() into the UNet, then export one ordinary graph.
  • Keep the unmerged adapter. Merge is not reversible from the baked file.
  • Export with coreai.diffusion.export or torch.export plus coreai-torch.
  • SD 1.x needs a multi-component folder: text encoder, UNet, and VAE parts.

Takeaways

  • Merge on CPU. Do not overwrite the adapter directory.
  • Confirm flags with --help and --dry-run. Do not invent a --model flag.
  • Exporting an unmerged PEFT wrapper will fail graph capture.