Environment setup
Use Python 3.11 or newer on macOS. uv manages the project and virtual environment. The device check picks Apple Silicon MPS when it is available, and CPU if it is not.
curl -LsSf https://astral.sh/uv/install.sh | sh
mkdir -p clothing-coreai && cd clothing-coreai
uv init --python 3.11
uv venv
uv add torch torchvision coreai-torch
uv run python -c "import torch, torchvision; print(torch.__version__, torchvision.__version__); print('MPS:', torch.backends.mps.is_available())"
uv run python train.pySave the lesson code as train.py or the named script for this lesson. uv run uses the project environment, so you do not need a global pip install.
It is tempting to treat training as a side project and conversion as the real engineering. In this stack, conversion starts asking questions that training must answer: what is the input shape, which classes mean what, which operations appear in the graph, and can the weights be loaded on the Mac that does the export?
Start from the exported shape
ToTensor() gives one grayscale channel with float values in 0...1. The small two-convolution, two-linear-layer ClothingCNN keeps the graph easy to follow and the exported input at [1, 1, 28, 28]. A fixed example tensor makes torch.export record the real interface. It does not hide a shape change until the app runs.
Keep the Mac path simple
Train with Apple Silicon MPS when it is available, or CPU if it is not. There is no CUDA step in this course. Before export, move the model to CPU, switch to evaluation mode, and save weights that the conversion environment can load. Do not save a device-specific file.
device = "mps" if torch.backends.mps.is_available() else "cpu"
model = ClothingCNN().to(device)
# train on Fashion-MNIST with ToTensor() and CrossEntropyLoss
# ...
model = model.to("cpu").eval()
torch.save(model.state_dict(), "clothing.pt")This is a device-policy sketch. Use the complete training recipe in the original course. Keep the saved file portable. Never write a CUDA-only load path for an Apple-silicon Core AI project.
Class order is part of the graph's meaning
The ten output positions only become labels through the fixed Fashion-MNIST order: T-shirt/top, Trouser, Pullover, Dress, Coat, Sandal, Shirt, Sneaker, Bag, Ankle boot. If the SwiftUI app changes the order, the runtime still succeeds, but the app shows the wrong label.
What training must give the layer below
- A model whose input and operations can be exported with the planned fixed shape.
- Weights saved on CPU so conversion can load them on the Mac without CUDA.
- A stable output class order that still holds after conversion and display.
- Preprocessing that matches the app's float32 0 to 1 tensor.
Now you have the whole chain, so you can rebuild it from the bottom up. Meet the original pipeline again, then open lesson 01 of the original course with the reversed map in mind.
Key concepts
- Conversion asks questions training must answer: shape, class order, ops, and portable weights.
- Fashion-MNIST
ToTensor()gives one grayscale channel, float32 0-1. - Train on MPS when available, else CPU. No CUDA. Save CPU weights in
clothing.pt. - The ten output positions map to Fashion-MNIST order through training, conversion, and UI.
Takeaways
- Move the model to CPU, call
eval(), then save weights the export environment can load. - Class order is part of the graph's meaning, not a SwiftUI decoration.
- Training preprocessing must match the app's float32 NCHW 0-1 tensor.