顯示具有 Apple Silicon 標籤的文章。 顯示所有文章
顯示具有 Apple Silicon 標籤的文章。 顯示所有文章

macOS 最快 TTS 實測:30 秒語音 379ms,Apple Neural Engine 才是隱藏王牌

Mac Studio 黑底霓虹綠終端風,audio waveform 從機器中散出

把 30 秒的英語語音算完,要多久?答案是 0.379 秒。在一台 Mac Studio(64GB)上,用 mattmireles/kokoro-coreml 把 Kokoro-82M 餵進 Apple Neural Engine,30 秒台詞 379 毫秒生完,等於 79 倍 realtime。同一台機器、同一個模型,丟給 MLX(GPU)跑要 763 毫秒,丟給 PyTorch CPU 慢到三五千毫秒——同模型不同 runtime,速度可以差一個數量級。

過去兩年寫過幾個 voice agent 的人應該都很熟那種痛:LLM 已經把 token 吐出來了,TTS 在那邊吭吭吭算半秒,整個對話節奏就斷掉。雲端 API 不是不能用,但 ElevenLabs Flash 官方公告 first-byte ~135ms ,加上網路 round-trip 直接破半秒。如果你的 Mac 就在桌上,這延遲根本沒道理付出去。

這篇把 2026 年中目前 macOS 上最快的幾個 TTS 工具實測數字攤開來講,順便分享一個多數人沒注意到的事實:Apple Neural Engine 才是 M 系列上真正的 TTS 速度王牌,而不是 GPU 或 MPS。

環境前置:本文所有實測數字來自 2026 年 6 月的公開 benchmark。硬體涵蓋 M1 Mini / M2 Air / Mac Studio(64GB) / M4 Pro。所有工具都是 100% on-device 本機推論,不打雲端 API。


速度排行榜:一表看完

先丟結論。下面這張是我把所有來源交叉驗證後整理出來的,數字都有出處可查:

排名工具加速後端實測速度模型中文支援
🥇kokoro-coremlApple Neural EngineMac Studio 64GB:30s 音檔 379 ms(79×RT);M1 Mini 14×RTKokoro-82M, 170 MB✅ 8 voices
🥈Supertonic 3ONNX Runtime(CPU / WebGPU)M4 Pro CPU 中等文 RTF 0.013(≈77×RT);長文峰值 RTF 0.006(≈167×RT)66~99M❌ 31 語言沒中文
🥉Piper TTSONNX Runtime(CPU)RTF 0.008(≈ 125×RT), ~22 MBVITS, 20M✅ zh_CN
4macOS say / AVSpeechSynthesizer系統內建啟動瞬間(無模型載入)內建✅ Mei-Jia / Ting-Ting
5MLX-Audio(Kokoro / Qwen3-TTS / Higgs)MLX(Metal GPU)Kokoro RTF 0.44;Qwen3-TTS RTF ~282M ~ 4B✅ Qwen3-TTS 中文業界頂尖

來源:mattmireles/kokoro-coreml 實測表 、Supertonic 3 官方 benchmark 、Soniqo Apple Silicon 對照 、sherpa-onnx Supertonic 對照表 。

這份表我自己整完有幾個地方讓我重看了三次。

最便宜的 M1 Mini(16GB,現在二手不到三萬台幣)跑 kokoro-coreml 也有 14× realtime。意思是你聽 1 秒語音,它只算 70 毫秒。這個延遲已經比你的鍵盤防彈跳還短,跑 voice agent 不會聽到「呃……」的卡頓。

Supertonic 3 的數字更誇張——M4 Pro 純 CPU 中等文本 RTF 0.013(≈ 77×RT),長文本峰值衝到 RTF 0.006(≈ 167×RT)。同一個 sherpa-onnx 對照表裡,ElevenLabs Flash v2.5 API 中等文本 RTF 0.077、OpenAI TTS-1 RTF 0.302、Gemini 2.5 Flash TTS RTF 0.673。一個 99M 參數的開源 ONNX 模型純 CPU 跑出 6 倍於 ElevenLabs、23 倍於 OpenAI 的速度,這在 2024 年根本不敢想。

被嚴重低估的還有 macOS 內建的 say。它的「啟動延遲」是真正的 0,因為根本不用載模型——系統永遠開著。對於 shell 通知、簡單無障礙場景,這還是最快的工具,下面會專段聊。


kokoro-coreml:把 ANE 真正榨乾

神經處理器晶片在黑色電路板上發出綠色資料光流

先講背景。Apple Neural Engine(ANE)這顆 NPU 從 A12 開始就裝在每一支 iPhone 跟每一台 M 系列 Mac 上,FLOPS 數字很漂亮、能耗也低——社群實測 LLM 工作負載大約是 GPU 的 1/10 (M4 Pro 上有人量到 ANE 2W、GPU 20W)。但開發者社群一直碰不到它——CoreML 是黑箱排程器,你連模型有沒有真的跑在 ANE 上都很難確認,更別說優化。

mattmireles/kokoro-coreml 把 Kokoro-82M 拆成 5 個子模型,每個子模型分派到最適合的處理器(ANE / CPU / GPU),用 Swift 的 MLModel(contentsOf:) 載 .mlpackage 就能跑。實測數字:

音檔長度M1 Mini (16GB)M2 Air (24GB)Mac Studio (64GB)
3 秒234 ms148 ms51 ms
10 秒686 ms466 ms126 ms
30 秒1,959 ms1,405 ms379 ms

對比同硬體上的 MLX-Audio (Metal GPU 路線):

音檔Mac Studio:CoreML vs MLX加速倍率
7 秒96 ms vs 224 ms2.3×
10 秒126 ms vs 289 ms2.3×
30 秒379 ms vs 763 ms2.0×

對比 PyTorch:

  • vs PyTorch MPS(GPU):1.8~3.4× 快
  • vs PyTorch CPU:3.5~7.3× 快

每個 bucket 都更快,越新的 silicon 差距越大——這正是 ANE 路線真正的潛力。GPU 你加再多核心也卡在記憶體頻寬,ANE 是專為神經網路設計的固定函式硬體,跑 conv / matmul 不會跟 GPU 搶 Metal queue。

整合到 Swift App 大概長這樣:

import CoreML

// 載入預編譯的 .mlpackage(5 個子模型)
let config = MLModelConfiguration()
config.computeUnits = .all  // 讓 CoreML 自動派 ANE/CPU/GPU

let textEncoder = try MLModel(contentsOf: textEncoderURL, configuration: config)
let durationPredictor = try MLModel(contentsOf: durationURL, configuration: config)
let decoder = try MLModel(contentsOf: decoderURL, configuration: config)
// ... 然後 pipeline 串起來

// 同步推論
let pcmBuffer = try synthesizer.synthesize(text: "Hello, world.")
// 在 M1 Mini 上這行回來大約 50ms(短句)

raw 數字、benchmark 腳本、跨 Mac 比較矩陣都在 GitHub ,要做 Swift app 整合直接拿來用。

唯一的坑:作者老實寫了,iPhone(A14 / A17 Pro)的 ANE 編譯器會拒絕完整的 ANE 計畫(ANECompile() FAILED),iPhone 版本只能用 staged policy——decoder-pre 跑 ANE、其他階段跑 CPU+GPU。但 M 系列 Mac 沒這個問題,本文聚焦的是 macOS。


Supertonic 3:純 CPU 也能贏雲端 API

CPU 形狀的賽車在賽博龐克高速公路上超車

如果 ANE 是「在 Apple 生態裡作弊」,那 Supertonic 3 就是「連 GPU 都不用,純 CPU 直接幹翻所有人」。

Supertonic 是 Supertone Inc.(韓國公司,被 HYBE 收購過)開源的 TTS,66~99M 參數、用 flow-matching 架構、純 ONNX Runtime 跑。實測在 M4 Pro 上:

系統RTF (mid 152 chars)chars/sec
Supertonic 3 (M4 Pro CPU)0.0131,048
Supertonic 3 (M4 Pro WebGPU)0.0071,801
ElevenLabs Flash v2.5(API)0.077209
OpenAI TTS-1(API)0.30255
Gemini 2.5 Flash TTS(API)0.67318
Kokoro(sherpa-onnx CPU)0.124107

數字來源:sherpa-onnx HuggingFace benchmark (同一頁有完整 API 對照)。

讓我們把這個數字放在桌上看清楚——Supertonic 用 M4 Pro 的 CPU,跑出比 ElevenLabs Flash 快 6 倍、比 OpenAI TTS-1 快 23 倍的速度。它甚至不需要 ANE,連 GPU 都不用。一個 99M 的 ONNX 模型輾過所有商業 TTS API,乾。

Flow matching 架構是它的祕密武器——傳統 autoregressive TTS(像 Tacotron、Bark)要 token by token 慢慢吐,flow matching 把生成重新定義成「從 noise ODE 解到 mel-spectrogram」的平行解碼問題。對 CPU 來說,平行化的工作量比 sequential token 友善太多。

裝起來就一行:

pip install supertonic
from supertonic import SupertonicTTS

tts = SupertonicTTS(language="en")
audio = tts.synthesize("Hello, this is Supertonic running on CPU.")
# audio 是 numpy array,可以直接寫 wav

但有個非常致命的限制:官方支援的 31 種語言 包含阿拉伯、保加利亞、捷克、丹麥、荷蘭、英、愛沙尼亞、芬蘭、法、德、希臘、印地、克羅埃西亞、匈、印尼、義、日、韓、拉脫維亞、立陶宛、波、葡、羅馬尼亞、俄、斯洛伐克、斯洛文尼亞、西、瑞典、土、烏克蘭、越南——就是沒有中文。對台灣讀者來說這個直接 deal-breaker,後面會講中文場景該怎麼選。


Piper 與 macOS say:老兵不死

黑色終端機浮在太空中顯示綠色音訊波形

聊完最快的兩隻,回來講「夠用但被低估」的兩個老兵。

Piper TTS

Piper (Rhasspy 開發、現在轉到 Open Home Foundation)是輕量場景的事實標準。VITS 架構、ONNX 編譯、medium voice ~22 MB、CPU 上 RTF 0.008(125 倍 realtime),30+ 語言含中文 zh_CN。Home Assistant 用它做語音助理、NVDA 螢幕閱讀器用它做無障礙、LocalAI 內建它做 TTS endpoint。

brew install piper-tts  # 或 pip install piper-tts
piper --model en_US-amy-medium --output_file out.wav <<< "Hello world"

它的優勢不在絕對速度(在 M-Mac 上 Supertonic 跟 kokoro-coreml 都比它快),而在「樹莓派級裝置都能跑即時 TTS」這件事。如果你想做家用智慧音箱、想在 Mac mini server 上跑 voice 服務、想嵌進低耗能設備,Piper 還是首選。

要注意的坑:原本的 rhasspy/piper repo 在 2025 年 10 月封存了,授權從 MIT 改成 GPL-3.0。如果你的產品要商用閉源發布,先確認新授權對你 OK 再用。

macOS 內建 say 跟 AVSpeechSynthesizer

這個老實說被嚴重低估。say 是 macOS 內建 CLI,啟動延遲是真正的 0——因為系統永遠把語音引擎掛在 background,根本不用載模型。

# shell 通知用法
sleep 30 && say -v Fiona "Build done, get back to work"

# 跑模型訓練不想盯著螢幕
python train.py && say "Training finished" || say "Training failed"

# 列出所有聲音
say -v "?"

macOS 26 開始,內建的「Premium / Neural Voice」品質已經非常 OK,繁體中文有 Mei-Jia(台灣國語)、粵語有 Sin-Ji、普通話有 Ting-Ting。設定 → 輔助使用 → 旁白 → 語音 → 自訂裡可以下載高品質版(每個約 200~500 MB)。

say -v Mei-Jia "你好世界,今天天氣真好"

對應的 Swift API 是 AVSpeechSynthesizer,可以調語速、音高、音量:

import AVFoundation

let synthesizer = AVSpeechSynthesizer()
let utterance = AVSpeechUtterance(string: "你好世界")
utterance.voice = AVSpeechSynthesisVoice(language: "zh-TW")
utterance.rate = 0.5
utterance.pitchMultiplier = 1.0
synthesizer.speak(utterance)

但這隻有個 voice agent 開發者必須知道的硬限制:AVSpeechSynthesizer 不能處理串流 partial text ——你必須把整段文字餵進去它才會開講。對 LLM token-by-token 輸出的場景完全不適用。Apple 至今沒補這個洞,這也是為什麼 voice agent 開發者寧可裝 kokoro-coreml 或 Supertonic 也不用內建 API。


中文場景:別被 Supertonic 騙了,Qwen3-TTS 才是王者

古典詩集冒出綠色音波

如果你是台灣讀者要做中文 TTS,前面 Supertonic 的速度數字看了會流口水,但記住——它沒中文。中文場景必須重新排名:

中文需求推薦工具為什麼
速度第一、中文夠用kokoro-coreml + zf_xiaoyi8 個普通話 voice,社群實測 zf_xiaoyi 達 A-、zm_yunxi B+;ANE 加速速度跟英文一樣
中文品質第一、可接受慢一點Qwen3-TTS-0.6B via MLX-Audio阿里出品、訓練數據海量、中文表現業界頂尖;M-Mac RTF ~2
零安裝、繁中say -v Mei-Jia台灣國語內建語者、無門檻
跑中文 audiobookMLX-Audio + Qwen3-TTSmyByways 有 ePub→audiobook 腳本 可參考
樹莓派 / HA 中文Piper zh_CN輕量、ONNX、長期維護

Qwen3-TTS 是這次研究中我覺得中文使用者最該認識的工具——阿里巴巴 Qwen 團隊 2026 年 1 月開源(Apache 2.0),支援 12Hz / 25Hz、0.6B / 1.7B 多檔位、3 秒語音 cloning、10+ 語言。在 SEED 中文測試上 WER 0.77(1.7B 12Hz),比 CosyVoice 3、F5-TTS、Spark TTS 都低。

Apple Silicon 透過 MLX-Audio 跑:

pip install mlx-audio
# 下載 8bit 量化版(M Mac 上記憶體友善)
huggingface-cli download mlx-community/Qwen3-TTS-12Hz-1.7B-CustomVoice-8bit

# 跑
python -m mlx_audio.tts.generate \
  --model mlx-community/Qwen3-TTS-12Hz-1.7B-CustomVoice-8bit \
  --text "你好,今天我們來聊聊 macOS 上最快的 TTS。" \
  --voice "default"

實測在 M2 macMini 大約「1,000 字元 / 分鐘」(myByways 實測 ),不是很快但中文品質直接屌打 Kokoro 中文 voice。如果你要做中文 podcast、audiobook、新聞播報,這就是目前 M-Mac 上最強的開源解。


不同情境怎麼選:決策樹

綠色光徑從中央節點分歧出去像決策樹

把所有工具放一起看會頭暈,下面是我給自己的決策邏輯:

你要做什麼?
│
├─ Shell 通知、無障礙、簡單腳本播報
│   └─→ macOS 內建 say(5 秒就能上手)
│
├─ Swift / Mac App 整合最快本機 TTS
│   ├─ 英語為主 →→→ kokoro-coreml(ANE)
│   └─ 中文為主 →→→ kokoro-coreml + zf_xiaoyi voice
│
├─ Python 跑 batch(audiobook / podcast)
│   ├─ 純英語、追速度 →→→ Supertonic 3
│   ├─ 中文品質要好 →→→ MLX-Audio + Qwen3-TTS
│   └─ 多語混合 →→→ MLX-Audio + Higgs v3 / OmniVoice
│
├─ Voice agent(要串流、LLM token-by-token)
│   ├─ 英語 →→→ sherpa-onnx + Supertonic
│   ├─ 中文 →→→ sherpa-onnx + Kokoro zh
│   └─ ⚠️ 千萬別用 AVSpeechSynthesizer(不能串流)
│
├─ 家庭自動化、Home Assistant、樹莓派
│   └─→ Piper TTS(zh_CN / en_US-amy-medium)
│
└─ 要 voice cloning(3 秒樣本複製音色)
    └─→ Qwen3-TTS via MLX-Audio(速度交換功能)

決策邏輯背後其實是兩條軸——「速度 vs 中文品質」跟「腳本場景 vs App 整合」。速度王是 ANE 跟 ONNX 兩條路線,中文品質王是阿里的 Qwen3-TTS(犧牲速度換來的),其餘工具都是在這兩端之間取捨。


一個你可能踩到的坑:runtime 換錯,速度差 10 倍

研究這個主題最讓我訝異的不是哪隻最快,而是同一個模型用不同 runtime,速度可以差到一個數量級。

以 Kokoro-82M 為例,同樣是 Mac Studio(64GB)、同樣的 voice af_heart、同樣的 30 秒測試文本:

Runtime完成時間相對速度
CoreML (ANE)379 ms1.0× baseline(最快)
MLX (Metal GPU)763 ms2× 慢
PyTorch MPS (GPU)700~1300 ms1.8~3.4× 慢
PyTorch CPU1300~2800 ms3.5~7.3× 慢

我看過太多人在 Reddit / HN 抱怨「Kokoro 在我的 Mac 上跑超慢」,結果一問,全部都是 PyTorch CPU 或 PyTorch MPS——這就像買了 Ferrari 用一檔開上高速公路。如果你的工作流會頻繁呼叫 TTS,花一個下午把 runtime 換到 CoreML 或 MLX,效能直接翻 2~7 倍。

決策原則記一下:

  • 要 絕對最快 → CoreML(限 Apple Silicon)
  • 要 跨平台 + 多模型 → MLX-Audio(限 Apple Silicon,但模型多)
  • 要 跨平台 + 跨硬體 → ONNX Runtime(CPU / WebGPU / iOS / Android 都通)
  • 要 完整生態、不挑硬體 → PyTorch(最慢但相容性最好)

寫 demo 用 PyTorch 沒問題,但要 ship 進產品請務必把模型轉成對應加速 runtime——這件事的 ROI 高到你不做白不做。


結語:本機 TTS 的世代交替

研究這個主題之前,我的預設是「本機 TTS 撐不住 production,雲端 API 還是必要」。研究完之後我整個立場反轉——M2 Air 上把 LLM token-by-token 接 sherpa-onnx 的 sentence-level Supertonic,中間用 VAD 切句、TTS 串流播放,整套 pipeline end-to-end 對話延遲打到 500 ms 以下完全有可能。這已經接近人類對話的自然節奏,雲端 API 加網路 round-trip 反而追不上。

中文場景目前是兩條路:要速度選 kokoro-coreml,要品質選 MLX-Audio + Qwen3-TTS。Supertonic 雖然數字嚇人但沒中文,官方 31 種語言列表 跳過 zh 我猜是訓練資料授權問題,期待下半年補上。

如果你還沒在自己的 Mac 上跑過任何一個本機 TTS,今天就去 clone 一個跑跑看。最低成本:開 terminal 打 say -v Mei-Jia "你好",五秒鐘體驗 macOS 內建語音的進化。進階:pip install supertonic 跑英語、pip install mlx-audio 跑 Qwen3-TTS 中文。最進階:拉 mattmireles/kokoro-coreml 進 Xcode 專案,把 voice agent 的 first-byte 延遲打到 50ms 以下。

雲端 TTS 不會死,但本機 TTS 在 2026 年已經不是「能用就好」的妥協選項——它是「比雲端更快、更便宜、更隱私」的明顯贏家。差別只是你願不願意花一個下午把 runtime 換對。


延伸閱讀

對這個主題有想討論的細節、或是你的 Mac 跑出不一樣的數字,歡迎來 bashcat.net 找我。

LibreCAD 在 macOS 26 Tahoe 打不開?這不是 LibreCAD 的鍋,是整個 codesigning 政策變了

實測環境:本機 crash report 抓到的真實環境如下——OS Version: macOS 26.5.1 (25F80)、Hardware Model: Mac15,6(Apple Silicon)、Homebrew cask LibreCAD 2.2.1.4 升級到 2.2.1.5、Termination Reason 為 Namespace CODESIGNING, Code 2, Invalid Page。版本號完整字串取自 sw_vers 與 crash report,並非虛構或預測版本;不同 minor 版的讀者照本文步驟操作應仍然適用,但出現新症狀時記得貼出自己的 crash report 來比對。

TL;DR:升級到 macOS Tahoe 後,舊版 Homebrew cask 的 LibreCAD 點兩下會瞬間崩。三行指令治本:brew reinstall --cask librecad → xattr -dr com.apple.quarantine → codesign --force --deep --sign -。但要知道,2026-09-01 之後這個 cask 會直接被 Homebrew 從本家停掉,所以順便講一下長期怎麼活下去。

LibreCAD 圖示在 Dock 跳一下就消失的視覺意象

我點兩下 LibreCAD 圖示。Dock 上的 icon 微微跳一下,然後沒了。沒有彈窗、沒有錯誤訊息、沒有「應用程式意外結束」的對話框,就純粹消失。ps aux | grep -i librecad 也是空的。

第一個直覺是:「啊不就 macOS 26 又把哪個權限收緊了」。打開 Console.app 看了一眼,crash report 寫得很乾脆:

Exception Type:    EXC_BAD_ACCESS (SIGKILL (Code Signature Invalid))
Termination Reason: Namespace CODESIGNING, Code 2, Invalid Page

夭壽,連 dyld 都還沒把第一個 dylib 載完就被 kernel 一刀砍了。這篇就是把整個排查到根因到修復走一遍,順便聊一下 Homebrew 在 2026 年丟出來的「Gatekeeper 一刀切」政策 後面的脈絡——因為這已經不是 LibreCAD 一個 app 的問題了,是接下來會有一卡車工具陸續陣亡。

點兩下沒反應,先別怪 LibreCAD

先把現場狀況做出來。我先確認 LibreCAD 還在:

ls /Applications/ | grep -i libre
# LibreCAD.app
# LibreOffice.app

App bundle 還在原位。再看 brew 怎麼說:

$ brew info --cask librecad
==> librecad (LibreCAD): 2.2.1.5
CAD application
https://librecad.org/
Deprecated because it does not pass the macOS Gatekeeper check!
It will be disabled on 2026-09-01.

兩條重點:已棄用、過不了 Gatekeeper。但這只是高層敘述,不是根因。直接戳簽章本身:

$ codesign -dv /Applications/LibreCAD.app
Executable=/Applications/LibreCAD.app/Contents/MacOS/LibreCAD
Identifier=LibreCAD
Format=app bundle with Mach-O thin (arm64)
CodeDirectory v=20400 size=74049 flags=0x20002(adhoc,linker-signed)
Signature=adhoc
Sealed Resources=none
Internal requirements=none

$ spctl -a -vv /Applications/LibreCAD.app
/Applications/LibreCAD.app: code has no resources but signature indicates they must be present

重點不是 Signature=adhoc——adhoc 簽章本身沒罪。是 Sealed Resources=none 加上 spctl 那句「code has no resources but signature indicates they must be present」。意思是 bundle 內附的簽章 metadata 宣稱「我這 bundle 有打包好的 sealed resources」,但實際 bundle 裡面對應的 _CodeSignature/CodeResources 檔案內容對不上。

這種「簽章說有,但實際對不到」的 mismatch 在以前的 macOS 上會被 codesign 容忍。但在 macOS 26.5 上,新的 codesigning monitor 直接在 dyld 第一次 mmap 載 page 時就觸發 fault,回報 Invalid Page——也就是 crash report 那一行 mapped file 1031c4000-103224000 [ 384K] r--/rwx SM=COW,page protection 對不上預期的 hash,kernel 立刻 SIGKILL。整個過程 LibreCAD 自己的 main() 連執行都還沒走到,所以你才會看到 dock 跳一下就沒了,連 console log 都沒留下。

為什麼以前能跑,升完 macOS 26 就崩?

macOS 26 codesigning crash report 抽象視覺化

如果你以為這是個別 app 的問題,那你只看到冰山一角。我順手 google 了一下 Namespace CODESIGNING Invalid Page,整個 macOS 26 升級季的回報就沒停過:

  • 有人回報 Xcode 升完 macOS 26 直接崩在啟動 ,crash 訊息與 LibreCAD 同款。需注意該帖至今未獲官方解法,DTS Engineer 建議的重裝、runFirstLaunch 都沒效——我引這條只是要佐證「同款 crash code 不限 LibreCAD」,不是指該帖有答案
  • 在 macOS Sonoma 14.5 期間就有 kitty 終端機 因 bundle 內檔案被改動觸發同樣的 SIGKILL (Code Signature Invalid)——這個案例不在 26 時代,但它清楚示範了「sealed resources 對不上就死」的機制,到了 26 只是門檻拉更低
  • Lakr233/vphone-cli #211 紀錄了一批舊 IPA 在 macOS 26 上全部 CODESIGNING Invalid Page 起手;要注意這是越獄/kernel patch 環境的特殊情境,不能直接類比一般使用者,但 crash code 完全相同,可以看出 kernel 那層判斷邏輯確實升級了

共通點:adhoc 簽章 + bundle 內容曾被 (重新) 寫過 / 抽換過 / 重打包過,導致 sealed resource hash 對不上現在的檔案內容。

這背後是 macOS 從 Sonoma → Sequoia → Tahoe 一路把 kernel-level codesigning monitor 收緊的結果。實際效果就是:以前 codesign 的 lazy validation(只在某些路徑驗)變成 eager validation(每個被 mmap 進來的 code page 都驗 hash)。一旦 hash 對不上,就 Invalid Page 給你看。

這邊要先承認一個對照觀點:Eclectic Light 的 Hoakley 在 2026 年 1 月寫 認為 Apple 目前並沒有預告要對 codesigning 推出更嚴格的新限制,他甚至覺得短期內收緊「不太可能」。我與他的差異在於:他談的是 Apple 的官方公告與政策層面(確實還沒有大動作),而我談的是實際使用者在新版上踩到的 kernel 行為差異——這兩件事不矛盾,但讀者要分清楚。Apple 不出 PR 不代表 kernel codesigning monitor 沒在背地裡更嚴。我這邊看到的證據是 crash report 與社群災情,而非 Apple 文件。

這也是為什麼 Homebrew 在 Discussion #6482 裡面用了「dumpster fire」這種字眼來形容 unsigned app 的 UX——他們不是要跟 Apple 站隊,是 Apple 從 kernel 把不合格的簽章 enforce 起來之後,那些靠 adhoc 簽章硬塞給使用者的 cask,再也不能「裝完就能跑」了。光是回報「裝不起來」的 issue 就會把他們淹死。

順帶一提,LibreCAD 這個 case 還有一個 cherry on top:cask 安裝過程中 Homebrew 會把 app 從 Caskroom 「搬」到 /Applications,搬完還會主動用 xattr 附加 com.apple.quarantine 旗標。搬移動作本身就會改動 bundle 的 mtime / 部分 metadata,後面再加上 quarantine xattr,對嚴格的 codesigning 驗證來說就是兩次外部介入,每一次都有可能讓 sealed resources 描述的「原始 build 樣貌」偏離。macOS 25 之前 kernel 對這類偏離還算容忍,26 一收緊就立刻起火。

三步驟治本:reinstall → 去隔離 → 重新 adhoc 簽

三步驟修復流程的意象圖

知道根因之後,治本就有方向了。三步驟按順序做,缺一不可:

# 1. 重灌 cask 拿最新版(2.2.1.5)
brew reinstall --cask librecad

# 2. 解除 quarantine 旗標(讓 Gatekeeper 不要在啟動時再驗一次)
xattr -dr com.apple.quarantine /Applications/LibreCAD.app

# 3. 重新做 adhoc 簽章,重建 sealed resources
codesign --force --deep --sign - /Applications/LibreCAD.app

我先說:只做前兩步是不夠的。我親自踩過——reinstall 完、quarantine 拔掉、open -a LibreCAD 結果還是同款 crash。crash report 依然是 Namespace CODESIGNING, Code 2, Invalid Page,乾。

為什麼?因為 quarantine 旗標只影響 Gatekeeper 在首次執行時會不會跳出「這 app 來自網路你確定要打開嗎」的對話框。把它拿掉只能避開那個 user-facing 流程,完全不影響 kernel 對 code page hash 的驗證。我這個 case 的問題是 sealed resources mismatch,那是 kernel 在 mmap 載 page 時就會 fail 的——對應到 crash report 那段 mapped file ... r--/rwx SM=COW 載入紀錄——跟 Gatekeeper 沒關係。

第三步 codesign --force --deep --sign - 才是治本。拆開來看:

  • --force:覆蓋既有簽章
  • --deep:遞迴對 bundle 內所有 nested code(plug-ins、frameworks、helper binaries)都重簽
  • --sign -:用「adhoc identity」簽——單一 dash 是 codesign 認的 magic value,定義可在本機 man codesign 查到(Apple 不提供穩定的線上 man page URL,社群可參考 Keith Smiley 維護的 Xcode man pages 非官方 mirror ,但仍以本機 man 為準)

重要的副作用是:codesign 重簽的時候會重新計算整個 bundle 的 sealed resources hash 並寫進 _CodeSignature/CodeResources。簽章宣稱的內容跟實際 bundle 對齊了,kernel 驗 page hash 就會過。

驗證簽章修好了:

$ codesign --verify --verbose=2 /Applications/LibreCAD.app
--prepared:/Applications/LibreCAD.app/Contents/PlugIns/printsupport/libcocoaprintersupport.dylib
--validated:/Applications/LibreCAD.app/Contents/PlugIns/printsupport/libcocoaprintersupport.dylib
--prepared:/Applications/LibreCAD.app/Contents/MacOS/ttf2lff
--validated:/Applications/LibreCAD.app/Contents/MacOS/ttf2lff
... (所有 dylib / framework 一路 validated)

$ open -a "LibreCAD"
$ ps aux | grep -i librecad | grep -v grep
oliver  79998  11.7  0.5 436277376 182336   ??  S   下午 1:21   0:01.04 /Applications/LibreCAD.app/Contents/MacOS/LibreCAD

PID 出來、記憶體 182MB、跑起來了。整個過程不到三分鐘。

這個指令不是 hack:LibreCAD 官方在 v2.2.1.3 release notes 就已經附上 xattr -rc LibreCAD.app && sudo codesign --force --deep --sign - LibreCAD.app 當作官方 workaround——他們很清楚 macOS 上的 unsigned build 會被打槍。2.2.1.5 release notes 沒重複這段文字,但同樣是未簽章的 dmg,所以邏輯一樣適用。我們這邊差別只是 cask 灌的、不需要 sudo,因為檔案 ownership 已經是 user 自己的。

Homebrew 為什麼要「一刀砍 387 個 cask」?

這邊我要把鏡頭拉遠一點。LibreCAD 不是孤例。根據 Homebrew 社群討論揭露的數字:

指標數字來源
Homebrew/cask 總 cask 數7,624Discussion #6482 維護者留言
因 Gatekeeper 失敗被棄用的 cask387Discussion #6482 維護者留言
占比約 5%由上兩列換算
強制 disable 日期2026-09-01brew info --cask librecad 直接讀 cask 檔案內 disable_date 程式碼欄位;release notes 措辭較模糊(「September 2026」),但個別 cask 的精確日期就是 9/1

這 387 個被點名的 cask 包含 LibreWolf、FreeTube、qutebrowser、cool-retro-term、love、darktable、powershell、qmk-toolbox 等等——很多是工程師日常工具。我自己就有 4、5 個中槍。

在 Discussion #6482 裡,Homebrew 維護者群輪番給出講白話的理由(這邊我刻意不點名到個別 ID,避免引言歸屬出錯——有興趣的人請自行翻原串):

The user experience for unsigned software is a dumpster fire.

另一位維護者補充說明:

Software installed from an official tap should just work, otherwise users are inclined to submit bug reports for things that are outside of Homebrew's control.

翻譯成台灣工程師的話就是「我們不想再幫 unsigned 上游擦屁股」。考慮到 Homebrew 完全是志工專案、每個 deprecated cask 後面都是 N 個「為什麼裝不起來」的 issue,這個決定不能說不合理。但對下游使用者來說,意思很直接:2026-09-01 之後,brew install --cask librecad 會直接給你「No available cask」。在此之前剩多少時間,請自己對照當下日期換算——倒數時鐘比寫一句固定的「還有 N 個月」實在多了。

另外維護者還補了一句:「Apple has no say over how we do anything in Homebrew」。也就是這次清洗不是 Apple 強迫的,是 Homebrew 自己對「使用者體驗 vs 維護成本」算出的天平。靠杯,但合理。

2026-09-01 之後怎麼辦?三條長期出路

棄用 cask 墓園與遠方燈塔的意象圖

不想等死,現在就要動。對 LibreCAD(以及其他類似情境)的長期策略,我整理出三條路:

路線 A:直接從 GitHub 下載官方 dmg + 自己 adhoc 簽

最直觀。到 LibreCAD/LibreCAD releases 抓最新 dmg,掛載後拖到 /Applications,然後跑:

xattr -dr com.apple.quarantine /Applications/LibreCAD.app
codesign --force --deep --sign - /Applications/LibreCAD.app

優點:版本完全跟上游,沒有 Homebrew 那層中介可能加的問題。

缺點:要手動升級。LibreCAD 官方 release 一樣沒簽 Developer ID(Issue #2054 有討論過 notarization,最後沒做),所以還是要 adhoc 簽。每次升級都要重做一次。

路線 B:自己開 tap 接手維護

Homebrew 在禁用聲明裡明說:「你可以自己開一個 tap、把這些 cask 撿回去用」。技術上:

# 假設你想自己維護 librecad cask
brew tap-new YOUR_GITHUB/casks
# 把 Homebrew/homebrew-cask 裡 librecad.rb 抄一份
# 移除 deprecate 那行
# brew install --cask YOUR_GITHUB/casks/librecad

優點:保留 brew 工作流,升級也吃 brew upgrade。

缺點:你要負責跟上 upstream 版本,並且每次更新都要驗證 cask 內附的簽章。一個人維護等於把 Homebrew 維護者拒絕做的事自己扛起來,我這肝大概撐不了。

路線 C:從 GitHub source 自己編

最硬派。git clone https://github.com/LibreCAD/LibreCAD.git → 裝 Qt6、boost、muparser → qmake6 → make。

小注意:Homebrew 裝的 Qt6 不會把 qmake6 直接放進預設 PATH,通常你需要 $(brew --prefix qt)/bin/qmake6 或自己 export PATH="$(brew --prefix qt)/bin:$PATH"。我第一次就被這個小細節絆到,浪費了 10 分鐘以為是 boost 沒裝對。

優點:你完全掌握。編譯產出的 binary 是 linker-signed(也是 adhoc 的一種),但因為是當下的 toolchain 簽的、sealed resources 是 fresh 的,跟 macOS 26 的 codesigning monitor 完全相容,不需要再手動跑 codesign。

缺點:要 30 分鐘起跳,相依套件不少,每次大版本升級都要 rebuild。如果你不熟 Qt6 build chain,第一次裝大概會踩 3、4 個坑。


我自己這次的決策過程其實有點掙扎。一開始我是想直接走路線 C 自己編——畢竟原則上最乾淨——但跑到 qmake6 階段卡了 PATH,又看到還要解 muparser 的 brew formula 跟 LibreCAD repo 內 librecad.pro 的相對路徑出入,我這肝就軟掉了。乾,後來想想 LibreCAD 我一年也開不到 5 次,花一小時編 source 完全不划算,最後落腳路線 A:抓官方 dmg + 手動 adhoc 簽。簡單、可重複、新版出來時知道怎麼補簽就好。

如果哪天我變成天天用 LibreCAD 的人,路線 B(自己開 tap)會比較划算——把 codesign 步驟寫進 cask 的 postflight,未來 brew upgrade 就會幫你自動補簽。路線 C 適合「我就是要改原始碼」的人,跟前兩條的用戶 persona 完全不同。三條路沒有絕對優劣,只有時間 / 折騰 / 自由度三軸的權衡。

Troubleshooting:常見踩坑

幾個我自己踩過或看別人踩過的坑:

Q1: 跑完三步驟還是 crash?

先確認 codesign --verify --verbose=2 /Applications/LibreCAD.app 有沒有報錯。如果中間任何一個 dylib 顯示 not validated 或 hash mismatch,代表 --deep 沒有走完。試試先用 sudo rm -rf /Applications/LibreCAD.app 砍掉再重灌,再簽一次。Apple Silicon 與 Intel 的二進位有時會混到,搬移時可能殘留舊檔。

Q2: 重新 adhoc 簽完,幾個月後又突然壞掉?

如果你裝了會主動掃 /Applications 的軟體(防毒、Time Machine 之外的某些備份工具、Sentinel 系列),它們可能會修改 bundle 內 timestamp 或 xattr,把 sealed resource hash 打偏。重跑第三步就好。或者更乾脆——寫一個 shell alias:

alias resign-librecad='codesign --force --deep --sign - /Applications/LibreCAD.app'

Q3: codesign 命令在我的機器報 errSecInternalComponent?

通常是 keychain 在搞鬼。先跑 security unlock-keychain ~/Library/Keychains/login.keychain-db 解鎖。Apple Silicon 上偶爾要重開機 codesign daemon 才會醒過來。

Q4: 為什麼不能直接 sudo spctl --global-disable 一次解掉?

兩個原因。一是這指令在 macOS Sequoia 之後需要先到「系統設定 → 隱私權與安全性」按實體按鈕確認,沒法純 CLI 解決。二更重要——它只關掉 Gatekeeper 對 unsigned app 的封鎖,並不關掉 kernel codesigning monitor。本 case 的問題在 kernel 那層,spctl 沒救。

Q5: 那 csrutil disable 呢?

別。SIP 關掉會把整個系統的安全基線打開,得不償失。本 case 不需要動到 SIP。

Q6: --deep flag 不是被 deprecated 了嗎?

技術上 Apple 在文件裡 是建議 distribution 流程用更精細的方式取代 --deep,但對「本機 adhoc 重簽」這個情境,--deep 仍然是最直接的選擇。Apple deprecate 它主要是針對 Developer ID 流程的可重現性問題,跟我們無關。

寫在最後

這次的事件對我來說,與其說是 LibreCAD 壞掉,不如說是一個提醒:Apple 在 kernel level 把 codesigning 從 advisory 變 mandatory 的這條路,沒有要回頭的意思。Sonoma → Sequoia → Tahoe 一路收緊,下一個版本只會更嚴。

對開發者來說,這代表「我隨手 build 一個 binary 給朋友裝」這種輕便的分享模式,在 macOS 上已經接近壽終正寢。要嘛你乖乖申請 Apple Developer 帳號(每年 99 美金)做 Developer ID 簽 + notarization,要嘛你的使用者就要學會自己 codesign --force --deep --sign -。沒有中間地帶。

對 macOS 使用者來說,這代表你必須多認識一點 codesign 的基本概念。不再是「裝了能跑就好」的時代了。當你下次看到 dock icon 跳一下就消失、console 沒留下任何訊息——直接打開 Console.app 找 crash report,搜尋 CODESIGNING、Invalid Page 這幾個關鍵字,大概率就是同款問題。

最後,2026-09-01 之後 Homebrew 會把這 387 個 cask 從本家停掉。我建議你現在就跑一遍:

brew list --cask | xargs -I{} brew info --cask {} 2>&1 | grep -B1 "deprecated.*Gatekeeper"

看看你的機器上有哪些 cask 在這份「死亡名單」上,提早決定要轉路線 A / B / C 哪一條。我自己跑完就發現我裝過的 LibreCAD、LibreWolf、qutebrowser、love2d 全都中槍,等到 9 月才在那邊靠杯,要一次學三個 workaround 真的很煩。

至於 LibreCAD 修不好的話——別只貼「打不開」,先去 ~/Library/Logs/DiagnosticReports/ 找最新的 LibreCAD-*.crash 或 .ips,把 Termination Reason 那行貼出來。沒這個我也只能猜,反正大概率還是 CODESIGNING / Invalid Page,那答案就是這篇文章的第三步。

參考資料

8GB 的 RK3588 能跑多聰明的 LLM?ROCK 5C 用 NPU 實測 5 個模型

先講結論,省得你滑到最後: 在一片 8GB 的 RK3588 板子上,用 NPU 跑得最聰明的是 Qwen3-4B-Instruct-2507,我出的 7 題全對,但每秒只吐 3.7 個 token。 想要順一點的對話體驗,Qwen3.5-2B 的 8.4 tok/s 是比...