# Build Troubleshooting — visionA-local > **這份文件的用途**:記錄 visionA-local 打包安裝檔(macOS / Windows / Linux)時實際踩過的雷, > 讓下次遇到同樣症狀能**快速定位**,而不是重新 debug 一輪。 > > **目標讀者**:未來要在 Windows / Linux build 這個專案安裝包的開發者(可能是你自己,也可能是團隊成員)。 > > **這不是** build 教學。build 流程本身看 `build-pipeline.md`(Makefile 骨架、vendor 目錄、CI 策略)。 > 這份是它的 troubleshooting 補充:**症狀 → 根因 → 為什麼會發生 → 解法 → 如何預防/根治**。 > > **紀錄來源**:2026-07 這輪(M10 classification + 特規打包 + 三平台實機 build)踩的雷, > 每條都對到 progress.md 或實際 code 的 `檔案:行號`。若與現況不符,以 code 為準。 --- ## 快速症狀索引表 先在這張表找你看到的症狀,跳到對應章節。**症狀常常不指向真正的根因**——這正是這份文件存在的理由。 | 你看到的症狀 | 可能的雷 | 跳到 | |-------------|---------|------| | 裝置 reset 後 `load_model Error code 24`、停在 `KDP2 Loader` | 開發模式 `dist/scripts/` 缺 `firmware/` | §1 | | 前端顯示「載入模型失敗」、terminal 出現 `darwin_usb.c:584` / SIGABRT | 除錯時 curl 與 UI 搶同一顆 USB | §2 | | Windows build:`go: command not found` / `wails: command not found` / `pnpm not found`(明明裝了)| MSYS2 login shell 重建 PATH | §3 | | Build 後置檢查失敗「預期 2 個 .nef,實際 8 個」 | `copy_bundled_data` 沒清空目標、殘留舊 .nef | §4 | | Windows 推論 `KP_ERROR_INVALID_PARAM_12`(Error 12) | input size 用了使用者亂填的 declared 值 | §5 | | 每次啟動都「Python 相依未通過」但又不重裝,怎樣都好不了 | venv 半套安裝、只檢查 python.exe 存在就跳過 | §6 | | 明明環境是好的,卻被判「Python 相依驗證失敗」擋住啟動 | 健康檢查 probe 沒補 Windows DLL 搜尋路徑 | §7 | | App 一啟動 pip 就秒退 `ResolutionImpossible`(certifi ×3 之類) | vendor wheels 累積多版本 | §8 | | Windows 中文標籤亂碼(「布」→「撣�」、log 出現 `�`) | Python stdio 綁 cp950 而非 UTF-8 | §9 | 如果症狀不在表上,先讀 §10 共通教訓——很多雷的「表面症狀」都不指向根因。 --- ## 環境前置需求(三平台) 打包前的一次性環境安裝,用 bootstrap 腳本,不要手動裝: | 平台 | 腳本 | 裝什麼 | |------|------|--------| | Windows | `scripts/bootstrap-windows.ps1` | winget 裝 git / go / node / pnpm / python / **MSYS2**(提供 bash + make)/ Inno Setup + wails | | Linux | `scripts/bootstrap-linux.sh` | apt 裝 go 1.22.5 / node 20 / pnpm / wails + GTK/WebKit/libusb dev headers | | macOS | (手動)| go / node / pnpm / wails / `brew install create-dmg`(DMG 美化,選用) | 三平台都需要 `make vendor-sync` 先把第三方二進位(Python runtime / wheels / ffmpeg)下載到 `vendor/`。 **⚠️ Windows 特別注意**:make 在 Windows 上是透過 `C:\msys64\usr\bin\bash.exe` 執行的(Windows 沒有原生 make), 這帶來 §3 的 PATH 坑。詳見該節。 --- ## 逐條 Troubleshooting ### §1 開發模式資源不同步 → 裝置 reset 後 load_model Error 24 **症狀** - 裝置首次連線、reset 後,`load_model` 回 `Error code 24`。 - Python bridge 卡在 `KDP2 Loader` 狀態、進不到 `KDP2 Comp`。 - **只在手動開發模式(`dist/scripts/`)發生,打包出來的安裝包沒這問題。** **根因** - 開發模式下 `dist/scripts/` 需要同時含 **`kneron_bridge.py` + `firmware/` + `drivers/`**,`dist/data/` 需含 `models.json` 與 `.nef`。 - 曾經只手動複製了 `kneron_bridge.py`、**漏掉 `firmware/`** → 裝置 reset 後 bridge 重啟時找不到 firmware → 停在 Loader → `load_model Error 24`。 **為什麼會發生** - 手動同步「就複製那個我改到的檔案」很直覺,但 bridge 執行期會去讀 `firmware/`、`drivers/` 這些不會每次都動、容易被忘記的相依資源。 - 打包流程之所以沒事,是因為 Makefile 三平台都用整包搬(`cp -R server/scripts/*` / `find | cp` 整包),不是逐檔挑。 **解法** - 開發模式同步時,`dist/scripts/` 一律帶齊 `kneron_bridge.py` + `firmware/` + `drivers/`,`dist/data/` 帶齊 `models.json` + `.nef`。不要只複製「這次改到的那個檔」。 **如何預防 / 根治** - 走打包流程驗證而不是手動同步:`make payload-macos`(progress.md L413 實跑驗證過 firmware/drivers 都在)。 - 已寫入 memory:`~/.claude/projects/-Users-jimchen-visionA/memory/project_local_tool_dev_env.md`。 - 來源:progress.md L409-414。 --- ### §2 除錯時 curl 戳 flash 與 UI 搶同一顆 USB → SIGABRT **症狀** - 使用者端顯示「載入模型失敗」。 - terminal 出現 libusb 的 assert:`darwin_usb.c:584`,程式 SIGABRT 直接掛掉。 **根因** - 除錯時用 `curl` 觸發 flash(load model),**同時**使用者還在用 UI 操作同一顆裝置 → 兩個行程同時對同一個 USB endpoint 下命令 → libusb 在 macOS 上直接 assert / abort。 **為什麼會發生** - USB 裝置不是可多路複用的資源。KneronPLUS SDK / libusb 沒有替你做互斥;兩個 client 同時搶就炸。 **解法 / 預防** - **除錯期間不要碰裝置**——要嘛用 curl 手動測、要嘛用 UI 測,不要兩個同時來。 - 這不是 code bug,是除錯操作紀律。記住這個 assert 訊息(`darwin_usb.c:584`),下次看到就知道是雙頭搶 USB,不用往 code 裡挖。 - 來源:progress.md L412。 --- ### §3 bootstrap-windows 的 MSYS2 PATH → Go / wails / node / pnpm 找不到 **症狀** - Windows build 時,`go` / `wails` / `node` / `pnpm` 明明用 winget 裝好了,跑 make 卻報 `command not found`。 **根因** - Windows 沒有原生 make,本專案透過 `C:\msys64\usr\bin\bash.exe -l`(**login shell**)來跑 Makefile。 - login shell 啟動時會重跑 `/etc/profile`,**把 PATH 整個重建**成 MSYS2 自己的一套,Windows 上用 winget 裝的工具目錄就這樣被洗掉了。 - `MSYS2_PATH_TYPE=inherit`(bootstrap 有設,`bootstrap-windows.ps1:74`)**只影響 MSYS2 自己的 shell 啟動器**(`msys2.exe` / `mingw64.exe`);直接呼叫 `bash.exe -l` 時它不生效。 **為什麼會發生** - `inherit` 的作用範圍與「直接呼叫 bash.exe -l」的實際入口不重疊,是一個很容易誤解的設定。這也是為什麼 Inno Setup 呼叫、Python 呼叫本來就必須各自手動 export PATH。 **解法** - 跟 Inno Setup 一樣,**明確把工具目錄轉成 MSYS2 路徑格式(`C:\foo` → `/c/foo`)後再 export**,補在 `$PATH` 之前。 - 實作見 `bootstrap-windows.ps1:73-113`(註解完整說明)、`:200`(去重合併成單一 export,避免多行 export 互相覆蓋)、`:284-294`。 **如何預防 / 根治** - 任何要在 MSYS2 `bash.exe -l` 下被找到的工具,都不能依賴 `inherit`,一律顯式 export 轉譯後的 MSYS2 路徑。 - 來源:`bootstrap-windows.ps1:73-113`、progress.md L1401(M7 同型坑的前身)。 --- ### §4 copy_bundled_data 不冪等 → 「預期 2 個,實際 8 個」 **症狀** - Build 後置檢查失敗:`!! ERROR: 預期 2 個 .nef,實際 8 個 !!`,build 中止。 **根因** - `payload-windows` / `payload-linux` **刻意不 `rm -rf` 整個 `payload//`**(因為 `build-server-*` 已先把 binary 放進 `bin/`),所以前次 build 的 `data/` 殘留會留著。 - 白名單機制(M5-a、`BUNDLED_NEFS` 只留 2 個 .nef)上線前,舊 build 會把 8 個 .nef 全複製進去。升級到白名單版本後再 build,就變成「新複製 2 個 + 殘留 6 個 = 8 個」,被後置檢查擋下。 - `payload-macos` 沒踩到,只是因為它有 `rm -rf payload/darwin`。 **為什麼這個後置檢查是「對的」** - installer(`installer/windows/visiona-local.iss`)是用 `data\* + recursesubdirs` **整包收**,殘留什麼就出貨什麼。所以「數 .nef 個數不符就 fail」是防止安裝包偷偷多帶不該帶的模型——這個 fail-loud 檢查要保留。 **解法** - `copy_bundled_data` helper 先**清空整個目標 data 目錄**再複製(步驟 (a)),確保重複執行結果一致、不受既有內容影響。 - 為什麼清整包而不是只清 `nef/`:目標目錄內容 100% 由這個 helper 產生(Makefile 中只有這裡寫入 `payload//data/`),沒有其他來源需要保留;只清 `nef/` 的話,未來從 `server/data/` 移除的其他檔案殘留仍會被 installer 整包打包出貨。清整包才是真正冪等。 - 實作 + 完整註解見 `Makefile:39-118`(`define copy_bundled_data`)。 **如何預防 / 根治** - helper 內建 fail-loud 後置檢查:白名單缺檔直接 fail(`Makefile:104-108`)、複製後驗 `models.json` 存在(`:101-103`)+ .nef 數量 == 白名單長度(`:113-116`)。 - **不用 rsync**:Windows CI 跑在 Git Bash(`windows-2022` + `shell: bash`),該環境**沒有 rsync** → 一律用 POSIX `find` / `cp`(`Makefile:71-72`)。 - 來源:`Makefile:39-118`、progress.md L112-124。 --- ### §5 input size 優先序錯 → Windows 推論 Error 12 **症狀** - Windows 上推論回 `KP_ERROR_INVALID_PARAM_12`(Error 12)。 - 同一個模型在 macOS 上正常。 **根因** - input size 的來源可信度排序,**declared(使用者在上傳表單手填的值)不能排在檔名解析之前**。 - 上傳表單的 `inputSize` 欄位長期沒有實際作用,使用者是「隨手填」的(實際案例:填 640×640,模型其實是 224×224)。 - KneronPLUS **3.1.2(Windows)不再提供 2.0.0(macOS)的 `shape_onnx` 屬性**,SDK 這層在 Windows 直接落空 → 於是**垃圾 declared 值成為實際採用值**,送進 NPU 得到 Error 12。 - 檔名的 `wNNNhNNN` 是模型編譯工具鏈產生的,沒有人為亂填空間,**明確解析出來時比 declared 可信**。 **為什麼是 Windows 才炸** - 見 §附錄「KneronPLUS 版本相容性」:macOS 用 2.0.0、Windows 用 3.1.2,`shape` 欄位在 3.x 搬進巢狀 union(`TensorDescriptor.tensor_shape_info.data`),舊寫法 `getattr(node, "shape_onnx")` 在 3.1.2 拋 AttributeError 被靜默吃掉 → SDK 來源落空 → 掉到 declared。 **解法** - input size 可信度排序改為:**SDK(0) → filename(1) → declared(2) → known-model-id(3) → default(4)**。declared 降到第 3。 - 常數與完整根因註解見 `kneron_bridge.py:351-371`(`INPUT_SIZE_SOURCE_RANK`)。 - 逐軸解析(不再沿用單一純量 `_model_input_size`,因為模型輸入不保證正方形):`kneron_bridge.py:159-193`。 - 相容兩版 SDK 的 shape 讀取(先試 3.x 巢狀 `tensor_shape_info`、再退平鋪欄位):`kneron_bridge.py:479-523`。 **如何預防 / 根治** - 實機驗收時 grep log 印出的 input size source(`kneron_bridge.py:347-349` 說明這是唯一能一眼看出尺寸怎麼來的線索)——尺寸錯掉時 NPU **不一定報錯**,可能只是安靜給錯結果。 - ⚠️ 這是 §10「湊巧正確」教訓的實例:舊版檔名猜 224 湊巧對,改成「更可信」的 declared 反而錯。 - 來源:`kneron_bridge.py:345-376, 479-523`。 --- ### §6 venv 半套安裝 → 永久卡住、怎樣都好不了 **症狀** - 每次啟動都跑「Python 相依驗證未通過」,但又不重裝、也沒有提示,怎樣都好不了。 - 只能手動刪掉 `runtime/venv` 才能恢復。 **根因** - 舊邏輯只檢查 `python.exe`(`venv/Scripts/python.exe` 或 `venv/bin/python3`)**存在**就跳過安裝。 - 但「python 執行檔存在」**不等於**「相依裝好了」:pip install 中途失敗(斷網 / 磁碟滿 / 防毒攔截)會留下 venv 與 python.exe 都在、但 `import kp` 失敗的**半套環境**。 - 於是每次啟動都「看到 python.exe → 跳過安裝 → 但 import 失敗」,永久卡死且無提示。 **解法** - venv 已存在的日常啟動路徑改為「驗健康、必要時嘗試修復」:`reuseExistingVenv`(`app.go:1078-1103`)。 - venv 在但相依看起來壞了 → **只補裝 wheels、不整個重建 venv**(重建要重解壓 ~100MB tarball,而失敗幾乎都出在 pip 階段):`app.go:1091-1093`。 - 修復失敗時記錄 warning 但**不阻斷啟動**(見 §7),改以現有 venv 繼續:`app.go:1096-1101`。 **如何預防 / 根治** - 狀態檢查要驗「真的能用」而非「檔案在」——這是 §10 的橫向教訓。 - 快路徑用標記檔 `venv-ready.txt` 記錄「wheels 已成功裝完 + 當前 wheels 指紋」,指紋不符才真的跑一次 import 驗證(`app.go:1105-1152`)。 - 來源:`app.go:995-1103`、progress.md(M4 之後的 fix)。 --- ### §7 健康檢查阻斷啟動(自造 regression)→ 健康的 venv 被判壞、擋住啟動 **症狀** - 環境明明是好的(bridge 實際能跑、能掃到裝置),app 卻因「Python 相依驗證失敗」擋住啟動。 **根因(這是修 §6 時自己造成的 regression)** - §6 的健康檢查 probe 裸跑 `python -c "import kp"`,**沒有補 Windows 的 DLL 搜尋路徑**。 - `kp` 在 import 時就會 `ctypes.CDLL` 載入 `kp/lib` 下的 native DLL(libkplus / libusb-1.0 / libwdi + MinGW runtime,共 6 個)。Windows 上這些 DLL 不在預設搜尋路徑、必須先 `add_dll_directory`。 - 真正跑 bridge 時 `kl720_driver.go` 的 `startPython()` 與 `platform_windows.go` 的 driver 安裝腳本**有**做這件事,但 probe 沒做 → 健康的環境也 import 失敗 → 被判壞掉 → 擋住啟動。 **解法** - probe 的環境要盡量貼近「真正跑 bridge 的環境」:`pythonProbeScript` 加一段 preamble,先把 `site-packages/*/lib` 掛進 `PATH` + `os.add_dll_directory` 再 import(`app.go:1182-1205`)。 - **更重要的原則:健康檢查絕不可以成為啟動的阻斷點。** 完整理由見 `app.go:999-1016`: 1. 這個 probe 會誤判(環境貼合度永遠不可能 100%)。 2. 就算真的壞了,讓 bridge 自己回報「掃不到裝置」這類具體錯誤,比在啟動前用一個間接的 probe 攔死更好——kneron_bridge.py 本來就把 import kp 失敗當可降級的情況處理。 - 型別設計落地這個原則:`reuseExistingVenv` **沒有 error 回傳值**(`app.go:1078-1081`)——型別本身就保證了「健康檢查不會阻斷啟動」。 **如何預防 / 根治** - 加任何「啟動前健康檢查」時,先問:檢查失敗會不會擋住啟動?如果會,它就有能力誤殺健康環境。健康檢查應該是「發警告 + 嘗試修復」,不是 gate。 - 來源:`app.go:999-1016, 1078-1103, 1154-1205`。 --- ### §8 wheels 多版本 → pip ResolutionImpossible、app 一啟動就秒退 **症狀** - App 首次啟動安裝依賴時,pip 在 ~2 秒內 `ResolutionImpossible` 失敗,使用者完全無法啟動。 - vendor 目錄裡同一套件有多顆(如 `certifi ×3`、`numpy ×2`、`idna ×2`)。 **根因** - `vendor-wheels*` 用 `pip download --dest`,而 `pip download` **只「補下載缺的」、不移除舊版**。 - upstream 每發一次新版,`vendor/wheels/` 就多留一顆 whl → 實機累積成 16 顆(正常應為 9 顆)。 - 這些多版本原封不動被 `payload-*` 複製進安裝包 → app 啟動 `pip install a.whl b.whl ...` 同時收到 `certifi 2026.2.25` 與 `2026.6.17` → 直接 ResolutionImpossible。 - **debug 成本放大**:這個 pip 錯誤還被 Makefile 的 Auto 分支吞掉(`|| echo WARN`),導致三輪來回都拿不到線索。 **解法(兩層都要)** - **源頭**:`clean_wheels_dir` helper 在 `vendor-wheels*` 前先清空並重建 `vendor/wheels//`,確保冪等、不累積多版本(`Makefile:143-166`,完整事故迴歸註解在 `:121-142`)。 - **救援**:app 端 `selectLatestWheelPerPackage` 對每個套件只保留版本最高的一顆再交給 pip(`app.go:1314-1325+`),能救「已出貨」的舊安裝包。 - 兩層都要有:源頭杜絕讓新安裝包乾淨、救援讓舊安裝包也能自癒。 **如何預防 / 根治** - 版本比較用逐段整數比較的簡化 PEP 440(`compareWheelVersions`,`app.go:1282-1312`);套件名依 PEP 503 正規化,讓 `opencv_python_headless` 與 `opencv-python-headless` 視為同套件(`normalizeDistName`,`app.go:1255-1271`)。 - **錯誤不要被吞掉**:這條的三輪 debug 成本就是 pip 錯誤被 `|| echo WARN` 吞掉造成的。詳見 §10。 - 來源:`Makefile:121-166`、`app.go:1207-1325`、progress.md L135-136。 --- ### §9 Windows 中文亂碼 → 標籤「布」變「撣�」 **症狀** - Windows(繁中)上 classification 標籤亂碼:「布」變「撣�」。 - stderr log 的中文與 em dash(U+2014)出現 `�`(如 `�X SDK did not report`)。 **根因** - Windows 的 Python 把 stdio 綁到「系統 ANSI code page」而非 UTF-8。繁中 Windows 的 ANSI code page 是 **cp950**。 - bridge 的 JSON-RPC 兩個方向**不對稱**: - **Go → Python (stdin)**:Go 的 `encoding/json` **不** escape 非 ASCII,`{"labels":["布"]}` 在 wire 上是 raw UTF-8 `e5 b8 83`。以 cp950 解碼得到「撣」+ U+FFFD(截圖的亂碼);嚴格模式下直接 UnicodeDecodeError 讓整個 bridge 掛掉。 - **Python → Go (stdout)**:`json.dumps` 預設 `ensure_ascii=True`,中文被 escape 成 `\uXXXX` 純 ASCII,所以這條「目前剛好沒壞」——但那是隱性依賴,任何人加上 `ensure_ascii=False` 就會壞。 - **stderr**:`_log()` 的中文與 em dash 現在就會壞。 **解法** - `_force_utf8_stdio()` 把 stdin / stdout / stderr **一律** `reconfigure(encoding="utf-8", errors="replace")`(`kneron_bridge.py:22-71`),不理會系統預設編碼。 - **在 module import 時就呼叫**(`kneron_bridge.py:71`),不是在 `main()` 裡——必須早於任何 I/O(`main()` 會 `os.dup()` stdout、import 期間的例外也走 stderr)。 - 用 `errors="replace"` 而非 `strict`:這是診斷 log 通道與協定通道,遇到極端無效位元組寧可看到一個 U+FFFD 也不要讓整個 bridge 因一行 log 而崩潰(bridge 掛掉 = 裝置失聯,比壞字元嚴重)。 **如何預防 / 根治** - 把「stdout 是 UTF-8」變成**顯式契約而非巧合**(連目前沒壞的 stdout 方向也一併綁定)。 - ⚠️ `PYTHONUTF8` 與 `PYTHONIOENCODING` 管的範圍不同:前者是整個 Python 的 UTF-8 mode(含檔案系統編碼等)、後者只管 stdio 的編碼。這裡用程式內 `reconfigure` 是最直接、不依賴環境變數是否被正確傳入的做法。 - 測試釘死:`server/scripts/test_kneron_bridge_encoding.py`。 - 來源:`kneron_bridge.py:22-71`。 --- ## §10 共通教訓(橫向 pattern) **這節比逐條 bug 更有價值**——上面 9 個雷裡反覆出現的幾個 pattern,下次寫 code / debug 時記住這些,能少踩一半。 ### 10.1 macOS 開發 ≠ Windows 實機 好幾個雷都是「macOS 有、Windows 沒有」或「兩邊行為不同」造成的。macOS 上測不到、只有 Windows 實機才會爆的類別: | 類別 | macOS | Windows | 踩到的雷 | |------|-------|---------|---------| | stdio 編碼 | 預設 UTF-8 | 預設 cp950(繁中 ANSI code page) | §9 中文亂碼 | | native DLL 搜尋路徑 | dyld(可用 ctypes 絕對路徑預載) | 需 `add_dll_directory`、不在預設路徑 | §7 健康檢查誤殺 | | KneronPLUS SDK 版本 | 2.0.0(有 `shape_onnx`) | 3.1.2(shape 搬進巢狀 union) | §5 input size Error 12 | | shell / make | 原生 bash + make | MSYS2 login shell 重建 PATH | §3 工具找不到 | | 既有環境 | 開發機常有現成工具 | 乾淨機、什麼都要 bootstrap | §3 / §4 | **原則**:任何「編碼 / DLL 路徑 / SDK 版本 / code page / shell」相關的東西,**不能只在 macOS 驗**,一定要 Windows 實機或至少想清楚兩邊差異。 ### 10.2 「檔案存在」≠「可用」 §6(venv 半套安裝)和 §7(健康檢查)都栽在這。 - venv 只檢查 `python.exe` 存在就跳過安裝 → 半套環境永久卡死。 - 狀態檢查要驗**「真的能用」**(實際 import / 實際跑一次),而非「檔案在」。 - 但驗「能用」時要注意 §7 的教訓:驗證環境要貼近真實執行環境,否則會誤殺健康的環境;而且**健康檢查不能是啟動阻斷點**。 ### 10.3 錯誤被吞掉會讓 debug 成本爆炸 §8(wheels 多版本)的三輪來回,根因是 pip 錯誤被 Makefile 的 `|| echo WARN` 分支吞掉,拿不到真正的錯誤訊息。 - 吞錯誤 = 把「一次就能定位」變成「三輪還在猜」。 - fail-loud 優於 fail-silent。§4 的後置檢查(數 .nef 個數不符就直接 fail)就是正面教材——它讓「安裝包偷偷多帶模型」在 build 期就爆,而不是等出貨後使用者才發現。 - 對照 §5:input size 錯掉時 NPU **不報錯只給錯結果**——這種「靜默失敗」最貴,所以要靠 log 印出 source 當唯一線索。 ### 10.4 「湊巧正確」的行為改動要格外小心 §5(input size 優先序)是經典案例: - 舊版用檔名猜測,某些模型湊巧猜到 224、剛好對。 - 改成「看起來更可信」的 declared(使用者手填值)反而錯——因為使用者是隨手填的。 - **教訓**:當你把一個「能動但你覺得不夠嚴謹」的邏輯改成「更正確」的版本時,先確認新來源真的更可信。「更正式的欄位」不代表「更可信的值」——declared 是正式欄位、但值是垃圾。 ### 10.5 兩層防護(源頭 + 救援) §8 的解法是兩層都做:`clean_wheels_dir` 從源頭杜絕(新安裝包乾淨)+ `selectLatestWheelPerPackage` 救援(舊安裝包自癒)。 - 只做源頭 → 已出貨的舊安裝包救不了。 - 只做救援 → 每個新安裝包都帶著髒 vendor 出貨、依賴 app 端補救。 - 涉及「已出貨產物」的問題,通常源頭與救援都要有。 --- ## §附錄 A:KneronPLUS 版本相容性 三平台的 KneronPLUS wheel 版本**不一致**,這是好幾個雷的隱形根因。寫任何碰 SDK 的 code 時必讀。 | 平台 | KneronPLUS wheel 版本 | 影響 | |------|----------------------|------| | macOS | 2.0.0 | `TensorDescriptor` 有平鋪的 `shape_onnx` / `shape_npu` 屬性 | | Linux | 2.0.0 | 同上 | | Windows | 3.1.2 | **無** `shape_onnx` 屬性;shape 搬進巢狀 `tensor_shape_info.data`(V1/V2 union);DLL 需求更嚴 | **具體差異與注意事項**: 1. **shape 欄位位置不同**(→ §5 Error 12) - 2.0.0:`node.shape_onnx` / `node.shape_npu` 直接可讀。 - 3.1.2:`node.tensor_shape_info.version`(`ModelTensorShapeInformationVersion`)+ `.data`(`TensorShapeInfoV1` 有 `shape_onnx`/`shape_npu`;`TensorShapeInfoV2` 只有 `.shape`,docstring 明寫是 ONNX shape)。 - 舊寫法 `getattr(node, "shape_onnx")` 在 3.1.2 拋 AttributeError → 被 except 靜默吃掉 → SDK 這層以為「模型沒帶 shape」,其實是讀錯欄位。 - 相容兩版的讀法見 `kneron_bridge.py:479-523`(先試巢狀、再退平鋪)。 2. **native DLL 需求**(→ §7 健康檢查誤殺) - `import kp` 在 Windows 會載入 `kp/lib` 下的 native DLL(libkplus / libusb-1.0 / libwdi + MinGW runtime,共 6 個),不在預設搜尋路徑 → 需 `add_dll_directory`。 - macOS 走 dyld,可用 ctypes 絕對路徑預載(`_preload_kneron_dylibs_macos`,`kneron_bridge.py:74+`)。 3. **wheel 三平台版本不一致本身是風險**(progress.md M9-6 findings L587) - 若未來要加 KL630/KL730 等新晶片支援,macOS/Linux 的 2.0.0 wheel **沒有**對應 enum,必須先升 wheel。 - `update_kdp_firmware_from_files` 在 3.1.2 Python wrapper 中不存在(warrenchen 是 ctypes 直打 .so C symbol),這類 API 差異在跨版本時要逐一確認。 **原則**:任何讀 SDK 結構(shape / enum / API 簽章)的 code,都要同時對 2.0.0 與 3.1.2 驗,或明確寫成「先試新版結構、失敗再退舊版」的相容寫法。 --- ## §附錄 B:Build 完驗收 Checklist Build 完一個安裝包後,**至少**驗這些(每項標了對應的雷,避免重蹈覆轍): - [ ] **.nef 數量正確**:打包版(不是開發模式)確認只帶白名單的 2 個 `.nef`(`FCOS Detection` / `Tiny YOLOv3`),不是 8 個。→ §4 (開發模式驗不出來:`server/data/nef/` 8 個都在、過濾器只濾「檔案不存在」,開發模式會顯示 7 個模型。M5 必須用打包版驗、progress.md L206。) - [ ] **models.json 有進安裝包**:啟動後 `/api/models` 回非空、log 印 `Loaded N built-in models`(不是 0)。 - [ ] **中文顯示正常**(Windows 繁中機):classification 標籤、log 的中文都不亂碼。→ §9 - [ ] **input size source 正確**:實機推論後 grep log 的 input size source,確認不是掉到 declared / default 撿到垃圾值;Windows 上尤其要看。→ §5 - [ ] **推論不報 Error 12 / Error 24**:Windows 推論不 Error 12(§5)、裝置 reset 後 load_model 不 Error 24(§1)。 - [ ] **Python venv 健康**:全新機器首次啟動能自動裝好 wheels(不 ResolutionImpossible §8);且啟動不被健康檢查誤擋(§7)。 - [ ] **driver 綁定**:Windows 上 KneronPLUS native DLL 能被載入(`import kp` 成功、掃得到裝置)。→ §7 / 附錄 A - [ ] **Wails 視窗確認**:**開 app window** 確認主 UI 是 Next.js 而非 splash / installer wizard / 白畫面(progress.md L1602 的歷史教訓:M1 只用瀏覽器連 localhost 驗、沒開 window,讓 wizard 殘留混過 M1-M6)。 **驗收原則**:不要只驗「server 有回應」——很多雷(§1 / §4 / §5 / §9)在 `/api/health` 200 的情況下照樣存在。要驗到「真的能推論 + 顯示正確」。 --- ## 相關文件 - `build-pipeline.md` — Makefile 骨架、vendor 目錄結構、CI 策略、版本號管理(build 流程本體)。 - progress.md(`.autoflow/progress.md`)— M10 各段開發紀錄與踩坑細節的一手來源。 - memory:`~/.claude/projects/-Users-jimchen-visionA/memory/project_local_tool_dev_env.md` — 開發模式資源同步坑。