最近在 Mac 上跑通義的文字生圖模型 Z-Image,用的是 Z-Image.swift——一個用原生 Swift 包起來的版本,底層跑 mlx-swift,直接吃 Apple GPU,不用另外裝 Python、PyTorch 或一堆 CUDA 相關套件。
會想試這條路線,理由跟之前在本機跑 LLM 差不多:東西留在自己電腦上、沒有 API 帳單,而且原生 Swift 寫起來跟一般寫 macOS App 沒兩樣,不用切去 Python 的世界。缺點也很直接:踩雷的時候找不到太多中文資料,錯誤訊息大多要自己拆。這篇就是記一下整個過程,包含卡住最久的那個問題。
先準備環境
Apple Silicon 的 Mac(M 系列晶片,Metal GPU 加速要靠它)
macOS 14 以上
完整的 Xcode,不是只裝 Command Line Tools——這點後面會說明為什麼一定要
Swift 6 工具鏈(Xcode 自帶)
第一次在新機器上跑,還可能要多裝一個東西:Metal Toolchain。這是 Xcode 16 之後把 Metal 編譯器獨立拆出來的元件,沒裝過的話會在編譯 shader 時失敗,跳出這行:
error: cannot execute tool 'metal' due to missing Metal Toolchain;
use: xcodebuild -downloadComponent MetalToolchain
照著訊息跑一次就好:
xcodebuild -downloadComponent MetalToolchain
這個元件大概 688MB,下載一次之後就不用再抓了。
寫一個最小範例
先建一個空的 SwiftPM 執行檔專案:
mkdir ZImageDemo && cd ZImageDemo
swift package init --type executable
Package.swift 加上 Z-Image.swift 這個依賴:
// swift-tools-version: 6.0
import PackageDescription
let package = Package(
name: "ZImageDemo",
platforms: [.macOS(.v14)],
dependencies: [
.package(url: "https://github.com/zhutao100/Z-Image.swift", branch: "main")
],
targets: [
.executableTarget(
name: "ZImageDemo",
dependencies: [
.product(name: "ZImage", package: "Z-Image.swift")
]
)
]
)
Sources/ZImageDemo/main.swift 大概十幾行就能跑通:
import Foundation
import ZImage
let pipeline = ZImagePipeline()
// 先下載(沒下載過的話)+載入模型到記憶體
try await pipeline.loadModel(modelSpec: "Tongyi-MAI/Z-Image-Turbo") { progress in
print("下載/載入中:\(progress.stage.rawValue) \(progress.percentComplete)%")
}
let request = ZImageGenerationRequest(
prompt: "A small orange cat sitting on a windowsill, soft morning light, minimalist illustration style",
negativePrompt: nil,
width: 1024,
height: 1024,
steps: 9,
seed: nil,
outputPath: URL(fileURLWithPath: "output.png"),
model: "Tongyi-MAI/Z-Image-Turbo"
)
_ = try await pipeline.generate(request) { progress in
print("產生中:\(progress.stage.rawValue) \(progress.percentComplete)%")
}
print("完成,輸出到 output.png")
Tongyi-MAI/Z-Image-Turbo 是加速過的版本,速度快、品質夠用,一般測試用這個就好;Tongyi-MAI/Z-Image 是原版,品質稍微好一點但慢不少。steps 我抓 9,Turbo 版本用低步數本來就是設計上的取捨。
跑起來:不能直接 swift run
到這裡,直覺會下 swift run,但這樣會編過、也能連上,然後在真的要用 GPU 跑推論的那一刻直接 crash,丟出這行:
MLX error: Failed to load the default metallib.
原因是 mlx-swift 的 Metal shader(.metal 檔)要編成 default.metallib,這一步只有 Xcode 的建置系統做得到,純指令列的 swift build/swift run 編不出來。mlx-swift 自己的 README 也寫得很清楚:command line SwiftPM 沒辦法編 Metal shader,最終一定要靠 Xcode。
好消息是不用真的開 Xcode 視窗,xcodebuild 就能吃單一個 Package.swift,自動生出對應的 scheme:
xcodebuild -list
會看到一個跟你套件同名的 scheme(這裡是 ZImageDemo)。接著直接建置:
xcodebuild build -scheme ZImageDemo -destination 'platform=macOS' \
-derivedDataPath .build/xcode-dd
第一次跑會連著把 Metal shader 一起編掉,比 swift build 慢一些,但編出來的執行檔放在:
.build/xcode-dd/Build/Products/Debug/ZImageDemo
直接執行這支檔案,才會是真的能跑 GPU 推論的版本:
.build/xcode-dd/Build/Products/Debug/ZImageDemo
幾個踩到的雷
一、下載進度卡在 0%,不代表當掉
第一次抓模型(尤其是沒加速過的 Tongyi-MAI/Z-Image,權重有好幾 GB)的時候,畫面會停在「Loading model 0%」很長一段時間,看起來像卡死。
實際上是背景真的在抓檔案,只是套件目前回報進度的方式是「一個檔案算一單位」,遇到單一個大檔案(例如整包 safetensors)下載到一半,進度就是不會動,要等這個檔案完整下完才會跳。不放心的話可以開活動監視器,或者用 lsof -p <pid> 看那個行程有沒有正在寫某個成長中的暫存檔——只要檔案大小持續在漲,就是還在抓,不是卡住。
二、下載到一半把程式關掉,不會接續
這個雷比較痛:如果模型正在下載時把程式砍掉(或當機),已經下載一部分的那個大檔案不會留著、下次也不會接續下載,會整包重新抓一次。已經完整下載完成的小檔案(設定檔、tokenizer 那些)才會被保留,不用重抓。
所以下載中盡量不要中斷。真的要中斷,有心理準備那個正在下的檔案要重來一次。
三、模型快取位置可以換,記得先想好放哪
模型預設抓到 ~/.cache/huggingface/hub,跟 Python 生態的 huggingface_hub 共用同一套快取目錄——如果你機器上已經用 Python 版工具下載過同樣的模型,Swift 這邊可以直接讀到,不用重抓。
想換位置(例如系統碟空間不夠,想存到外接硬碟),設環境變數就好:
export HF_HUB_CACHE=/Volumes/External/hf-cache
要注意的是換路徑之後,新路徑底下如果沒有對應模型的快取,等於要重新下載一次,不是搬移舊的過去。
小結
整體來說,原生 Swift + MLX 這條路線可用,速度也不差,比想像中好上手。真正卡人的不是 API 或模型本身,而是「純 SwiftPM 編不出 Metal shader」這件事——不知道要用 xcodebuild 的話,可能會一直卡在同一個 crash 訊息上打轉。剩下下載進度顯示不夠即時、中斷不續傳這幾點,比較像是套件現階段的粗糙之處,繞得過去,只是要有心理準備。
相簿:Z-Image 產圖風格試跑
同一顆 Tongyi-MAI/Z-Image-Turbo,9 步、guidance_scale=0,換不同 prompt 風格跑出來的樣子,順手記錄一下:










9 步、無 CFG 的 Turbo 版本,對寫實攝影跟等角商務插圖這類「構圖規則、色塊清楚」的風格掌握得比較穩;水墨、線稿這種靠留白跟筆觸節奏的風格,偶爾會需要多抽幾次種子才會滿意。