docs: align Design.md with Phase 1 architecture

Restructure section 2 from component overview to tech selection table
covering Scheduler, Worker, Queue, Job State, Artifact Store, Web UI.
Document why Redis Stream for the queue (language-neutral, consumer
groups, single Redis instance, Crash Reset alignment) and detail
worker horizontal scaling via consumer groups.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
jim800121chen 2026-05-01 10:55:14 +08:00
parent 4d381c0b50
commit 7404ca9bc8

View File

@ -37,78 +37,131 @@
--- ---
## 2. 系統元件與責任 ## 2. 技術選型
### 2.1 Web UI | 元件 | 技術 | 說明 |
|------|------|------|
| Task Scheduler | **Node.js / Express** | I/O 密集、event loop 適合調度角色 |
| Worker (ONNX/BIE/NEF) | **Python / FastAPI** | 需要 Kneron Toolchain Python 環境 |
| Task Queue | **Redis Stream** | 語言中立Node.js 與 Python 都是 Redis client支援 Consumer Group 做水平擴展 |
| Job State | **Redis Hash** | 與 Queue 共用同一個 Redis instance重啟即清空 |
| Artifact Store | **Shared Volume**(單機)/ MinIO跨機未來 | Worker 之間的檔案交換區 |
| Web UI | **Vue 3 + Vite** | 前端 SPA |
### 2.1 為什麼 Queue 用 Redis Stream
- **語言中立**Scheduler (Node.js) 用 `ioredis`Worker (Python) 用 `redis-py`,雙方只需約定 queue name 與 message 格式
- **Consumer Group**:同一個 Consumer Group 內,每筆訊息只會被一個 Worker 消費,天然支援水平擴展
- **不額外引入元件**Queue 與 Job State 共用同一個 Redis部署簡單
- **符合 Crash Reset 哲學**Redis 不開 persistence重啟即清空
### 2.2 Worker 水平擴展Consumer Group
```
queue:onnx
└─ Consumer Group: "onnx-workers"
├─ onnx-worker-1 ← XREADGROUP 拿到 Job A
├─ onnx-worker-2 ← XREADGROUP 拿到 Job B
└─ onnx-worker-3 ← XREADGROUP 拿到 Job C
queue:bie
└─ Consumer Group: "bie-workers"
├─ bie-worker-1
└─ bie-worker-2
queue:nef
└─ Consumer Group: "nef-workers"
└─ nef-worker-1
```
- 同一個 Consumer Group 內,每筆訊息**只會被一個 Worker 拿到**Redis 自動分配
- 擴展只需多啟 Worker process例如 `docker-compose up --scale bie-worker=3`),不需改程式碼
- 每個 Worker 一次只處理一個 job透過 `XACK` 確認完成
---
## 3. 系統元件與責任
### 3.1 Web UI
- 提供使用者: - 提供使用者:
- 上傳 model.onnx、quant.zip - 上傳 model.onnx、quant.zip
- 建立轉換任務 - 建立轉換任務
- 查詢任務狀態 - 查詢任務狀態
- 下載 BIE / NEF - 下載 BIE / NEF
- Web UI 不直接讀寫 Redis 或 Queue - Web UI 不直接讀寫 Redis 或 Queue
- Web UI 可直接與 MinIO 上傳 / 下載(或透過 Scheduler 取得路徑) - Web UI 透過 Scheduler API 上傳檔案 / 下載結果
- 狀態更新:以 SSE 為主Polling 為備援3-5 秒) - 狀態更新:以 SSE 為主Polling 為備援3-5 秒)
### 2.2 Task Scheduler控制面 ### 3.2 Task Scheduler控制面Node.js
- 唯一的控制面元件 - 唯一的控制面元件
- 職責: - 職責:
- 建立 job_id - 提供 REST API建立 job、查詢狀態
- 建立 Redis job record - 建立 job_idUUID v4
- 將 job 推入對應 task queue - 建立 Redis job recordHash
- 接收 worker 完成事件done queue - 將 job 推入對應 task queue`XADD queue:onnx`
- 監聽 worker 完成事件(`XREADGROUP queue:done`
- 推進 job 狀態ONNX → BIE → NEF → COMPLETED - 推進 job 狀態ONNX → BIE → NEF → COMPLETED
- 提供 SSE 事件串流
- 流程固定ONNX → BIE → NEF不支援跳過或只跑部分 - 流程固定ONNX → BIE → NEF不支援跳過或只跑部分
- Scheduler 不等待 worker、不執行 heavy CPU 任務 - Scheduler 不等待 worker、不執行 heavy CPU 任務
### 2.3 Task Queue ### 3.3 Task QueueRedis Stream
- 至少包含以下 queue - Queue 列表:
- queue:onnx - `queue:onnx` — ONNX 轉換任務
- queue:bie - `queue:bie` — BIE 量化任務
- queue:nef - `queue:nef` — NEF 編譯任務
- queue:done - `queue:done` — Worker 完成回報
- 每個 queue 對應一個 Consumer Group
- Queue 僅用於「暫存」任務(重啟可清空) - Queue 僅用於「暫存」任務(重啟可清空)
### 2.4 Worker Pools ### 3.4 Worker PoolsPython
- ONNX / BIE / NEF Worker 各自獨立 pool - ONNX / BIE / NEF Worker 各自獨立 pool
- Worker 特性: - Worker 特性:
- 無狀態 - 無狀態
- pull-based自行從 queue 取任務) - pull-based`XREADGROUP` 從 queue 取任務)
- 一次只處理一個 job - 一次只處理一個 job
- 加入 Consumer Group自動分配任務
- Worker 失敗: - Worker 失敗:
- 回報 fail - 回報 fail`queue:done`
- Scheduler 將 job 標記為 FAILED - Scheduler 將 job 標記為 FAILED
### 2.5 MinIOArtifact Store ### 3.5 Artifact StoreShared Volume
- 所有 job 的 input / output / log 都放在 MinIO - 所有 Worker 掛載同一個 Docker volume`/data/jobs`
- 作為跨 worker / 跨主機的唯一資料交換區 - 每個 job 的檔案存放在 `/data/jobs/{job_id}/`
- Worker 之間透過檔案系統直接讀寫,無需上傳/下載
- 單機部署Docker named volume 或 bind mount
- 未來跨機部署:可替換為 MinIO只需改 Worker I/O 層
--- ---
## 3. 命名與資料結構 ## 4. 命名與資料結構
### 3.1 Job ID ### 4.1 Job ID
- 使用 UUID v4 - 使用 UUID v4
- 全系統唯一 - 全系統唯一
### 3.2 MinIO 路徑規則(建議) ### 4.2 Job 工作目錄結構
Buckettoolchain-jobs 掛載路徑:`/data/jobs`
``` ```
jobs/{job_id}/ /data/jobs/{job_id}/
input/ input/
model.onnx model.onnx # 使用者上傳的模型檔
quant.zip ref_images/ # 量化用參考圖片
output/ *.jpg / *.png
result.bie out.onnx # ONNX Worker 產出
result.nef out.bie # BIE Worker 產出
out.nef # NEF Worker 產出
logs/ logs/
onnx.log onnx.log
bie.log bie.log
nef.log nef.log
``` ```
### 3.3 Redis Job RecordHash / JSON 所有 Worker 共用同一個 volume直接讀寫同一個 job 目錄。
### 4.3 Redis Job RecordHash / JSON
Keyjob:{job_id} Keyjob:{job_id}
@ -129,26 +182,68 @@ error:
--- ---
## 4. 成功流程Happy Path ### 4.4 Redis Stream Queue 協定
Scheduler 與 Worker 之間透過 Redis Stream 溝通,需約定以下格式:
#### Task MessageScheduler → Worker Queue
```json
{
"job_id": "uuid-v4",
"created_at": "ISO-8601",
"input_dir": "jobs/{job_id}/",
"parameters": {
"model_id": 1,
"version": "0001",
"platform": "720",
"enable_evaluate": false,
"enable_sim_fp": false
}
}
```
#### Done MessageWorker → queue:done
```json
{
"job_id": "uuid-v4",
"step": "onnx|bie|nef",
"result": "ok|fail",
"reason": "optional error message",
"output_files": ["out.onnx"],
"completed_at": "ISO-8601"
}
```
#### Consumer Group 命名規則
| Queue | Consumer Group | Consumer 命名 |
|-------|---------------|--------------|
| `queue:onnx` | `onnx-workers` | `onnx-worker-{hostname}` |
| `queue:bie` | `bie-workers` | `bie-worker-{hostname}` |
| `queue:nef` | `nef-workers` | `nef-worker-{hostname}` |
| `queue:done` | `scheduler` | `scheduler-{hostname}` |
---
## 5. 成功流程Happy Path
1. 使用者透過 Web UI 建立轉換任務 1. 使用者透過 Web UI 建立轉換任務
2. Web UI 上傳 input 到 MinIO 2. Web UI 上傳 input 到 SchedulerScheduler 存入 `/data/jobs/{job_id}/input/`
3. Scheduler 建立 job recordpush 到 queue:onnx 3. Scheduler 建立 job recordpush 到 queue:onnx
4. ONNX worker pull 任務、執行、push done 4. ONNX worker pull 任務、從 job 目錄讀取 input、執行、寫出 out.onnx、push done
5. Scheduler 推進狀態push 到 queue:bie 5. Scheduler 推進狀態push 到 queue:bie
6. BIE worker 執行、輸出 result.bie、push done 6. BIE worker 從同一個 job 目錄讀取 out.onnx + ref_images、執行、寫出 out.bie、push done
7. Scheduler 推進狀態push 到 queue:nef 7. Scheduler 推進狀態push 到 queue:nef
8. NEF worker 執行、輸出 result.nef、push done 8. NEF worker 從 job 目錄讀取 out.bie、執行、寫出 out.nef、push done
9. Scheduler 標記 COMPLETED 9. Scheduler 標記 COMPLETED
10. Web UI 取得下載路徑 10. Web UI 取得下載路徑
--- ---
## 4.1 工作目錄與 Worker I/O 規格(落地版) ## 5.1 工作目錄與 Worker I/O 規格(落地版)
以下為實作落地時的檔案布局與 worker 互動規格(與 MinIO 路徑可一對一對應): 以下為實作落地時的檔案布局與 worker 互動規格(與 MinIO 路徑可一對一對應):
### 4.1.1 工作目錄 ### 5.1.1 工作目錄
- API 取號後建立 `task_id` - API 取號後建立 `task_id`
- API 將使用者上傳檔案放入工作目錄: - API 將使用者上傳檔案放入工作目錄:
- `{base_path}/{task_id}/` - `{base_path}/{task_id}/`
@ -161,7 +256,7 @@ error:
<image files...> <image files...>
``` ```
### 4.1.2 ONNX Worker ### 5.1.2 ONNX Worker
- 輸入:工作目錄下的唯一檔案(不假設檔名 / 副檔名) - 輸入:工作目錄下的唯一檔案(不假設檔名 / 副檔名)
- 輸出:`out.onnx` - 輸出:`out.onnx`
- 輸出位置:同一工作目錄 - 輸出位置:同一工作目錄
@ -169,21 +264,21 @@ error:
- `enable_evaluate` (default: `false`):是否執行 IP evaluator原 Web GUI 流程為 OFF - `enable_evaluate` (default: `false`):是否執行 IP evaluator原 Web GUI 流程為 OFF
- `enable_sim_fp` (default: `false`):是否執行浮點 E2E 模擬(尚未接線) - `enable_sim_fp` (default: `false`):是否執行浮點 E2E 模擬(尚未接線)
### 4.1.3 BIE Worker ### 5.1.3 BIE Worker
- 輸入:`out.onnx` + `ref_images/*` - 輸入:`out.onnx` + `ref_images/*`
- 輸出:`out.bie` - 輸出:`out.bie`
- 輸出位置:同一工作目錄 - 輸出位置:同一工作目錄
- 可選旗標: - 可選旗標:
- `enable_sim_fixed` (default: `false`):是否執行定點 E2E 模擬(尚未接線) - `enable_sim_fixed` (default: `false`):是否執行定點 E2E 模擬(尚未接線)
### 4.1.4 NEF Worker ### 5.1.4 NEF Worker
- 輸入:`out.bie` - 輸入:`out.bie`
- 輸出:`out.nef` - 輸出:`out.nef`
- 輸出位置:同一工作目錄 - 輸出位置:同一工作目錄
- 可選旗標: - 可選旗標:
- `enable_sim_hw` (default: `false`):是否執行硬體 E2E 模擬(尚未接線) - `enable_sim_hw` (default: `false`):是否執行硬體 E2E 模擬(尚未接線)
### 4.1.6 流程預設開關對照(原 Web GUI vs 現在 Workers ### 5.1.5 流程預設開關對照(原 Web GUI vs 現在 Workers
| 步驟 | 原 Web GUI 預設 | 現在 Workers 預設 | 開關 | | 步驟 | 原 Web GUI 預設 | 現在 Workers 預設 | 開關 |
|---|---|---|---| |---|---|---|---|
| ONNX 轉換/最佳化 | ON | ON | 無 | | ONNX 轉換/最佳化 | ON | ON | 無 |
@ -194,13 +289,13 @@ error:
| NEF Compile | ON | ON | 無 | | NEF Compile | ON | ON | 無 |
| HW E2E 模擬 | OFF | OFF | `enable_sim_hw` | | HW E2E 模擬 | OFF | OFF | `enable_sim_hw` |
### 4.1.5 Core / Toolchain 路徑一致性 ### 5.1.6 Core / Toolchain 路徑一致性
- Worker 需將工作目錄 path 傳給 core - Worker 需將工作目錄 path 傳給 core
- Core 需設定 toolchain 相關 path輸出與中間檔都指向該工作目錄 - Core 需設定 toolchain 相關 path輸出與中間檔都指向該工作目錄
--- ---
## 5. 失敗行為(簡化) ## 6. 失敗行為(簡化)
- Worker 例外 → 回報 fail - Worker 例外 → 回報 fail
- Scheduler 標記 job 為 FAILED - Scheduler 標記 job 為 FAILED
@ -209,7 +304,7 @@ error:
--- ---
## 6. APIScheduler ## 7. APIScheduler
### POST /jobs ### POST /jobs
建立新 job 建立新 job
@ -258,13 +353,14 @@ SSE 事件串流(主動推送狀態更新)
--- ---
## 7. Worker 行為規範 ## 8. Worker 行為規範
- 從對應 queue pull job_id - 從對應 queue pull job_id`XREADGROUP`
- 從 MinIO 下載 input - 從 shared volume 讀取 input`/data/jobs/{job_id}/`
- 執行 Toolchain flow - 執行 Toolchain flow
- 上傳 output - 將 output 寫入同一個 job 目錄
- push done event - `XACK` 確認訊息已處理
- push done event 到 `queue:done`
``` ```
{ {
"job_id": "...", "job_id": "...",
@ -276,7 +372,7 @@ SSE 事件串流(主動推送狀態更新)
--- ---
## 8. 併發與限流 ## 9. 併發與限流
- BIE Worker 數量 = 最大同時 BIE 任務數 - BIE Worker 數量 = 最大同時 BIE 任務數
- 每個 BIE Worker 一次只跑一個 job - 每個 BIE Worker 一次只跑一個 job
@ -284,7 +380,7 @@ SSE 事件串流(主動推送狀態更新)
--- ---
## 9. 最小部署建議 ## 10. 最小部署建議
- web-ui x1 - web-ui x1
- scheduler x1 - scheduler x1
@ -296,9 +392,121 @@ SSE 事件串流(主動推送狀態更新)
--- ---
## 10. MVP 驗證項目 ## 11. 本地開發與測試
- 單一 job 成功完成 ### 11.1 docker-compose 一鍵啟動
提供 `docker-compose.yml`,一個指令啟動整套系統:
```bash
docker-compose up
```
服務清單:
```yaml
volumes:
job-data: # 所有 Worker + Scheduler 共用的 job 工作目錄
services:
redis:
image: redis:7-alpine
ports: ["6379:6379"]
# 不開 persistence符合 Crash Reset 哲學
scheduler:
build: ./apps/task-scheduler
ports: ["4000:4000"]
depends_on: [redis]
volumes:
- job-data:/data/jobs
environment:
- REDIS_URL=redis://redis:6379
- JOB_DATA_DIR=/data/jobs
onnx-worker:
build: ./services/workers/onnx
depends_on: [redis]
volumes:
- job-data:/data/jobs
environment:
- REDIS_URL=redis://redis:6379
- JOB_DATA_DIR=/data/jobs
- WORKER_MODE=real # real | stub
bie-worker:
build: ./services/workers/bie
depends_on: [redis]
volumes:
- job-data:/data/jobs
environment:
- REDIS_URL=redis://redis:6379
- JOB_DATA_DIR=/data/jobs
- WORKER_MODE=real
nef-worker:
build: ./services/workers/nef
depends_on: [redis]
volumes:
- job-data:/data/jobs
environment:
- REDIS_URL=redis://redis:6379
- JOB_DATA_DIR=/data/jobs
- WORKER_MODE=real
web-ui:
build: ./apps/web
ports: ["3000:3000"]
depends_on: [scheduler]
```
水平擴展 Worker
```bash
docker-compose up --scale bie-worker=3
```
### 11.2 Stub Worker 模式
開發 Scheduler 或 Web UI 時,不需要啟動真正的 Kneron Toolchain 環境。
透過環境變數 `WORKER_MODE=stub` 切換為 Stub 模式:
```bash
# 啟動整套系統Worker 使用 stub 模式(不需要 toolchain 環境)
WORKER_MODE=stub docker-compose up
```
Stub Worker 行為:
- 收到任務後 sleep 數秒(模擬處理時間)
- 產生假的輸出檔案(空檔或固定內容)
- 回報成功到 `queue:done`
- 不依賴任何 Kneron Toolchain 二進位檔
```python
# Stub 實作範例
async def process_onnx_core_stub(input_paths, output_path, parameters):
await asyncio.sleep(3) # 模擬處理時間
stub_file = output_path / "out.onnx"
stub_file.write_bytes(b"STUB_ONNX_OUTPUT")
return {"file_path": str(stub_file), "file_size": stub_file.stat().st_size}
```
### 11.3 開發情境對照
| 開發情境 | 需要什麼 | 指令 |
|---------|---------|------|
| 改 Scheduler 邏輯 | Redis + stub workers | `WORKER_MODE=stub docker-compose up redis scheduler onnx-worker bie-worker nef-worker` |
| 改 Web UI | Redis + Scheduler + stub workers | `WORKER_MODE=stub docker-compose up` |
| 改 Worker 核心邏輯 | DevContainer + Redis跑單元測試 | `docker run -d redis && pytest tests/` |
| 完整 E2E 測試 | docker-compose真實 Worker | `docker-compose up` |
| 測試水平擴展 | docker-compose + scale | `docker-compose up --scale bie-worker=3` |
---
## 12. MVP 驗證項目
- 單一 job 成功完成ONNX → BIE → NEF → COMPLETED
- 多 job 排隊BIE 不超量 - 多 job 排隊BIE 不超量
- Worker 失敗 → job FAILED - Worker 失敗 → job FAILED
- Redis 重啟 → job 消失UI 提示重送 - Redis 重啟 → job 消失UI 提示重送
- Stub Worker 模式可正常走完完整流程
- Worker 水平擴展scale=3可正常分配任務