docs: 新增三平台 build troubleshooting 參考

把 M10 這輪三平台 build 踩的雷整理成 troubleshooting 文件,供未來
build 安裝包時參考。

涵蓋 9 條逐項 troubleshooting(統一格式:症狀 → 根因 → 為什麼發生
→ 解法 → 如何根治)、快速症狀索引表、5 條橫向教訓、KneronPLUS
2.0.0 vs 3.1.2 版本相容附錄、build 驗收 checklist。

核心教訓:症狀常不指向根因(Error 12 是 input size、pip 秒退是 wheels
多版本、中文亂碼是 stdio code page),macOS 開發環境測不到大量
Windows-only 問題(編碼、DLL 路徑、SDK 版本差異)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
jim800121chen 2026-07-24 09:08:13 +08:00
parent f62141a925
commit 9397a4d31f

View File

@ -0,0 +1,371 @@
# 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 中文標籤亂碼「布」→「撣<E3808C>」、log 出現 `<60>` | 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` 觸發 flashload 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 L1401M7 同型坑的前身)。
---
### §4 copy_bundled_data 不冪等 → 「預期 2 個,實際 8 個」
**症狀**
- Build 後置檢查失敗:`!! ERROR: 預期 2 個 .nef實際 8 個 !!`build 中止。
**根因**
- `payload-windows` / `payload-linux` **刻意不 `rm -rf` 整個 `payload/<os>/`**(因為 `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/<os>/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.2Windows不再提供 2.0.0macOS的 `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.mdM4 之後的 fix
---
### §7 健康檢查阻斷啟動(自造 regression→ 健康的 venv 被判壞、擋住啟動
**症狀**
- 環境明明是好的bridge 實際能跑、能掃到裝置app 卻因「Python 相依驗證失敗」擋住啟動。
**根因(這是修 §6 時自己造成的 regression**
- §6 的健康檢查 probe 裸跑 `python -c "import kp"`**沒有補 Windows 的 DLL 搜尋路徑**。
- `kp` 在 import 時就會 `ctypes.CDLL` 載入 `kp/lib` 下的 native DLLlibkplus / 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/<os>` 就多留一顆 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/<os>/`,確保冪等、不累積多版本(`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 中文亂碼 → 標籤「布」變「撣<E3808C>
**症狀**
- Windows繁中上 classification 標籤亂碼「布」變「撣<E3808C>」。
- stderr log 的中文與 em dashU+2014出現 `<60>`(如 `<60>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.2shape 搬進巢狀 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 「檔案存在」≠「可用」
§6venv 半套安裝)和 §7健康檢查都栽在這。
- venv 只檢查 `python.exe` 存在就跳過安裝 → 半套環境永久卡死。
- 狀態檢查要驗**「真的能用」**(實際 import / 實際跑一次),而非「檔案在」。
- 但驗「能用」時要注意 §7 的教訓:驗證環境要貼近真實執行環境,否則會誤殺健康的環境;而且**健康檢查不能是啟動阻斷點**。
### 10.3 錯誤被吞掉會讓 debug 成本爆炸
§8wheels 多版本)的三輪來回,根因是 pip 錯誤被 Makefile 的 `|| echo WARN` 分支吞掉,拿不到真正的錯誤訊息。
- 吞錯誤 = 把「一次就能定位」變成「三輪還在猜」。
- fail-loud 優於 fail-silent。§4 的後置檢查(數 .nef 個數不符就直接 fail就是正面教材——它讓「安裝包偷偷多帶模型」在 build 期就爆,而不是等出貨後使用者才發現。
- 對照 §5input size 錯掉時 NPU **不報錯只給錯結果**——這種「靜默失敗」最貴,所以要靠 log 印出 source 當唯一線索。
### 10.4 「湊巧正確」的行為改動要格外小心
§5input size 優先序)是經典案例:
- 舊版用檔名猜測,某些模型湊巧猜到 224、剛好對。
- 改成「看起來更可信」的 declared使用者手填值反而錯——因為使用者是隨手填的。
- **教訓**當你把一個「能動但你覺得不夠嚴謹」的邏輯改成「更正確」的版本時先確認新來源真的更可信。「更正式的欄位」不代表「更可信的值」——declared 是正式欄位、但值是垃圾。
### 10.5 兩層防護(源頭 + 救援)
§8 的解法是兩層都做:`clean_wheels_dir` 從源頭杜絕(新安裝包乾淨)+ `selectLatestWheelPerPackage` 救援(舊安裝包自癒)。
- 只做源頭 → 已出貨的舊安裝包救不了。
- 只做救援 → 每個新安裝包都帶著髒 vendor 出貨、依賴 app 端補救。
- 涉及「已出貨產物」的問題,通常源頭與救援都要有。
---
## §附錄 AKneronPLUS 版本相容性
三平台的 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 unionDLL 需求更嚴 |
**具體差異與注意事項**
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 DLLlibkplus / 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 驗,或明確寫成「先試新版結構、失敗再退舊版」的相容寫法。
---
## §附錄 BBuild 完驗收 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` — 開發模式資源同步坑。