Skip to content

Commit a88b99c

Browse files
vividfclaude
andcommitted
docs: newcomer READMEs for deployment and quantization modules
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 4ded075 commit a88b99c

2 files changed

Lines changed: 355 additions & 0 deletions

File tree

autoware_ml/deployment/README.md

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
# Deployment 模組:從 checkpoint 到 TensorRT engine
2+
3+
> 讀者設定:第一次接觸這個框架的人。讀完你應該能:跑一次 deploy、看懂產物、
4+
> 知道出錯時去哪裡找原因、以及替新模型接上這條 pipeline。
5+
> 量化(PTQ/QAT)另有專文:[`../quantization/README.md`](../quantization/README.md);
6+
> 訓練與資料面見 `docs/contributing/adding-models.md`
7+
8+
## 0. 三分鐘版
9+
10+
這個模組做一件事:**把訓練好的 PyTorch 模型變成 TensorRT engine,並「證明」它沒有變壞**
11+
一條命令走完全部:
12+
13+
```bash
14+
autoware-ml deploy \
15+
--config-name experiments/detection3d/centerpoint/<experiment> \
16+
--weights <checkpoint.ckpt>
17+
```
18+
19+
它依序做五件事,每一步的產物都落在 experiment 目錄:
20+
21+
```text
22+
export 每個 GraphStage 一份 <stage>.onnx
23+
precision fp16 化(自動選路:AutoCast / Q/DQ island cast / 原樣)
24+
build 每份 onnx 一顆 <stage>.engine(TensorRT,一律 strongly typed)
25+
verify 跨 backend 逐 tensor 比對(pytorch vs onnx vs tensorrt),過不了就 FAIL
26+
evaluate 三個 backend 各跑一次完整 metric(mAP/mIoU)+ 每 stage latency 表
27+
```
28+
29+
心智模型一句話:**模型自己宣告「我怎麼拆成可匯出的圖」(stage graph),框架負責
30+
把每張圖推過 export→build→verify→evaluate,三種 backend 用同一條 pipeline 執行。**
31+
32+
## 1. 核心概念
33+
34+
### 1.1 Stage graph:模型自述怎麼拆(`stages.py`)
35+
36+
一個模型的 deploy 面 = 一個 `build_stages()` 方法,回傳 stage 序列。只有兩種 stage:
37+
38+
- **`GraphStage`**:一張可匯出的子圖 = 一份 ONNX = 一顆 engine。宣告
39+
`name / module / inputs / outputs`,inputs/outputs 的名字**就是** ONNX 的 IO 名,
40+
值從 `StageContext`(一個跨 stage 的 name→tensor 字典)取放。
41+
- **`TorchStage`**:不可匯出的膠水(前處理、voxelize、scatter……),永遠跑 PyTorch,
42+
簽名 `fn(context) -> {name: value}`
43+
44+
為什麼要拆:因為真實模型不是一張圖——中間有 sparse conv(需要 plugin)、有動態
45+
shape 的索引計算、有根本不該進圖的預處理。stage graph 把「哪裡可以是圖、哪裡必須是
46+
torch」變成模型的**宣告**,pipeline 照宣告執行,誰都不用改框架。
47+
48+
`GraphStage` 的進階欄位(用到才看):`torch_fallback_backends`(某 backend 跑不了這
49+
張圖時退回 torch module,例:spconv 圖在 ONNX Runtime)、`onnx_dynamic_axes`(點雲類
50+
模型天生的動態維度)、`onnx_transforms`(這張圖固有的匯出後重寫,如 bias+activation
51+
摺進 plugin 節點)、`output_fields`(最終 stage 的輸出如何餵給 `assemble_predictions`)。
52+
53+
### 1.2 Backend 抽象:同一條 pipeline,三種執行體(`pipeline.py`, `backends/`)
54+
55+
`StagedPipeline` 對每個 backend 用同一套 stage 序列跑推論,差別只在 GraphStage 的
56+
執行體是誰:
57+
58+
| backend | GraphStage 跑什麼 | 用途 |
59+
| --- | --- | --- |
60+
| `pytorch` | 原 torch module | 基準真值 |
61+
| `onnx` | ONNX Runtime session | 驗證匯出圖的語意 |
62+
| `tensorrt` | TRT engine | 交付形態 |
63+
64+
artifact 命名規則:`artifact_path(output_dir, stage_name, backend)`
65+
`<experiment>/<stage>.onnx` / `<stage>.engine`。latency 表裡每個 stage 一行、
66+
`model_graphs` 一行(所有 GraphStage 合計)。
67+
68+
### 1.3 「same plan everywhere」不變量
69+
70+
量化模型的 checkpoint 是**自描述**的(placement record 內嵌),所以 `deploy`
71+
`test` **不讀 `cfg.quantization`**——給什麼 ckpt 就 deploy 什麼。這保證訓練、量化、
72+
部署三處看到的是同一個模型結構,歷史上的「校準時圖長 A 樣、匯出時長 B 樣」類 bug
73+
被這個不變量整類消滅。
74+
75+
### 1.4 CLI:一個命令名、每個 config family 一個實作
76+
77+
`deploy` / `test` / `train` / `quantize` 由 config 路徑前綴(`experiments/...`)分派
78+
到對應 family 的實作。所以不管什麼模型,命令長得一樣。
79+
80+
## 2. 一次 deploy 實際發生什麼
81+
82+
1. **build_stages()**:載入 ckpt(量化 ckpt 會先按 placement record 重建量化結構),
83+
模型回傳 stage 序列;`validate_stages` 檢查名字唯一、宣告完整。
84+
2. **export**:每個 GraphStage `torch.onnx.export`(opset 17),IO 名即宣告名。
85+
3. **precision pass**(`onnx/precision.py`,自動路由,模型端零程式碼):
86+
87+
| 圖的事實 | 走哪條 | 原因 |
88+
| --- | --- | --- |
89+
| 有自訂 domain(plugin) | 自家 island cast(整圖無島) | AutoCast 用 TRT parser 型別推導,不認 plugin op |
90+
| 有 Q/DQ(INT8/FP8) | 自家 island cast(fp32 島 + fp16 海) | AutoCast 拒收 Q/DQ 模型;island 是正確性地基,見 §3 |
91+
| 純圖 | modelopt AutoCast | 有數值守門(逐節點比對容差) |
92+
| `deploy.onnx.precision: fp32` | 原樣 | |
93+
94+
4. **TensorRT build**(`backends/tensorrt_builder.py`):**一律 strongly typed**——
95+
engine 的精度由 ONNX 圖的型別決定,不由 builder flag 猜。這是刻意決策:weak-typed
96+
`FP16` flag 會讓 TRT 的 kernel 自選精度,量化模型上曾實測翻車;strongly typed
97+
把精度變成**圖上可審查的事實**。plugin(`libautoware_tensorrt_plugins.so`)在
98+
build 前載入。
99+
5. **verification**(§4)→ 6. **evaluation**(§5)。
100+
101+
## 3. Precision:fp16 的海、fp32 的島
102+
103+
量化圖的 fp16 化**不是**全圖轉型。Q/DQ 及其周邊保持 fp32-typed(「島」),其餘轉
104+
fp16(「海」)。三層規則:
105+
106+
**誰進島**(`_quantized_island_names`,4 條依序):
107+
108+
1. 所有 Q/DQ 節點;
109+
2. scale/zero-point 的 producer(fp32 scale 位元組級保留——scale 就是量化本身);
110+
3. 每個 DQ 輸出的消費者(被量化的 Conv/Gemm 本體);
111+
4. 反向生長:從每個 Q 的 data 輸入沿 **float data 邊**往回穿過 commuting whitelist
112+
(`Relu/Add/Concat/MaxPool/Reshape/Transpose/Gather/...`),讓「量化 op → pointwise
113+
→ 下一個 Q」整段零 cast。
114+
115+
**cast 放哪**:只在「島↔海」與「圖 IO」邊界,每條跨界 float 邊恰好一顆;整數邊
116+
(zero-point、shape、indices)永不 cast(`_ISLAND_FLOAT_INPUT_SLOTS` 顯式表 +
117+
import 時 assert 與 whitelist 鎖死);圖 IO 保 fp32(runtime ABI)。
118+
119+
**為什麼**(每條都是量出來的):
120+
121+
| 規則 | 違反的實測代價 |
122+
| --- | --- |
123+
| scale 保 fp32 | fp16-typed Q/DQ 踩 TRT 10.8/10.16 缺陷:合併 scale subnormal → 融合 kernel 產 NaN、build 零警告(PTv3 mIoU 0.73→0.075) |
124+
| DQ→消費者直連 | TRT INT8 融合 pattern 對不上,build assert |
125+
| 鏈到下一個 Q 零 cast | Q-propagation 被 Cast 擋住 → 量化 conv 具現化 fp32:同一 backbone 4.76 vs 3.87 ms |
126+
| 海全 fp16 | 未量化區跑 fp32:CenterPoint 6.75 vs 4.44 ms |
127+
128+
**最重要的心智模型:島的 fp32 是「記號」不是執行精度。** TRT 把島內
129+
`DQ→Conv→Relu→Q` 融合成 int8 進出的 kernel;實際執行 = 海 fp16、島 int8、邊界幾顆
130+
cast(實測合計 0.118 ms)。fp16-typed Q/DQ(opset 19 合法、ORT 算得對)在 TRT 上是
131+
**NO-GO**,完整證據與重測工具:`work_dirs/reviews/fp16-typed-qdq-nogo.md`
132+
133+
出現 `Quantized chain breaks at ...` 警告時:該 op 若量化可交換 → 加進
134+
`_QDQ_COMMUTING_OPS`(連 slot 表一行,少一半 import 直接爆)並重跑三模型 battery;
135+
不可交換(LayerNorm/Gelu 類)→ 加 `_KNOWN_NON_COMMUTING_OPS` 消音。
136+
137+
## 4. Verification:比對哲學
138+
139+
`verification/` 對 config 宣告的 scenario(如 `pytorch(cuda) vs tensorrt(cuda)`)
140+
逐 tensor 比 max_diff。要點:
141+
142+
- **tolerance 是實測校準的,不是猜的**。量化/FP16 stage 的 raw-logit 跨 backend 差
143+
是預期行為(fake-quant vs 真 int8 kernel 的捨入路徑不同),**metric 相等才是真
144+
gate**。首跑 fail 時,錯誤訊息會給建議 gate(observed×1.25);把 observed 記進
145+
config 註解。
146+
- 預設 tolerance 故意嚴,逼每個新模型做一次有意識的校準,而不是繼承一個形同虛設的
147+
大數字。
148+
149+
## 5. Evaluation:三 backend 全量 metric + latency
150+
151+
`deploy.evaluation` 用同一個 dataloader 對三個 backend 各跑一次完整 metric,輸出
152+
並排(pytorch / onnx / tensorrt 三欄)。latency 表逐 stage 一行:看 `model_graphs`
153+
(圖部分合計)評估量化/精度收益,看個別 stage 找瓶頸。ONNX Runtime 跑不了 plugin
154+
stage(用 `torch_fallback_backends`)與 FP8 trt-domain op(該 experiment 直接關
155+
onnx backend)。
156+
157+
## 6. 新增一個模型的 deploy 面
158+
159+
1. 在模型類實作 `build_stages()`:先全 TorchStage 跑通 pytorch backend,再逐段換成
160+
GraphStage。
161+
2. experiment config 加 `deploy:` 區塊(參考 centerpoint / bevfusion / ptv3 現例):
162+
`onnx.precision``tensorrt.enabled`、verification scenarios、evaluation backends。
163+
3.`deploy deploy.tensorrt.enabled=false` 驗 onnx 正確性,再開 TRT。
164+
4. verification 首跑 fail → 按 §4 校準 tolerance。
165+
5. 有 sparse conv / 自訂 op → plugin 見 `docs/`(TRT plugin 建置)與
166+
`onnx_transforms` 現例(bevfusion sparse)。
167+
6. 要量化 → 讀 [`../quantization/README.md`](../quantization/README.md) 的 checklist。
168+
169+
## 7. 檔案地圖
170+
171+
```text
172+
deployment/
173+
stages.py TorchStage / GraphStage / StageContext / validate_stages
174+
pipeline.py StagedPipeline(三 backend 同一條)、PipelineCache、計時
175+
export.py deploy 流程編排(export→precision→build→verify→evaluate)
176+
onnx/
177+
export.py torch.onnx.export 包裝
178+
precision.py precision pass 路由、island 規則、commuting whitelist(§3 全部)
179+
modify.py config 驅動的圖手術(deploy.onnx.modify_graph)
180+
backends/
181+
tensorrt_builder.py strongly-typed build、plugin 載入
182+
tensorrt_runner.py engine 執行
183+
onnx_runner.py ORT 執行
184+
verification/
185+
backend_verifier.py scenario 執行
186+
output_comparator.py 逐 tensor 比對、建議 gate
187+
config.py deploy config schema
188+
```
189+
190+
## 8. 深挖
191+
192+
- 量化(宣告、PTQ/QAT、INT8/FP8 選擇):`../quantization/README.md`
193+
- fp16-typed Q/DQ NO-GO 全案(TRT NaN 缺陷、重測工具):`work_dirs/reviews/fp16-typed-qdq-nogo.md`
194+
- 三模型量化交叉驗證數字:`work_dirs/reviews/` 下各 README
195+
- 模型/訓練/資料面:`docs/contributing/adding-models.md`

autoware_ml/quantization/README.md

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
# Quantization 模組:宣告式量化(PTQ / QAT,INT8 / FP8)
2+
3+
> 讀者設定:第一次接觸這個框架的人。讀完你應該能:對現有模型跑一次 PTQ、看懂
4+
> placement 輸出、知道 QAT 什麼時候值得、以及替新模型接上量化。
5+
> 部署面(export / TensorRT / verification)見
6+
> [`../deployment/README.md`](../deployment/README.md)
7+
8+
## 0. 三分鐘版
9+
10+
```bash
11+
# 1) 先看不燒 GPU 的 placement(強烈建議)
12+
autoware-ml quantize --config-name experiments/.../<model>_int8 \
13+
--weights <fp_training.ckpt> +quantization.dry_run=true
14+
15+
# 2) PTQ:替換模組 → 校準 → 存自描述 checkpoint
16+
autoware-ml quantize --config-name experiments/.../<model>_int8 --weights <fp_training.ckpt>
17+
18+
# 3) 部署(不需要任何 quantization config——ckpt 自己知道自己是什麼)
19+
autoware-ml deploy --config-name experiments/.../<model>_int8 --weights <.../ptq.ckpt>
20+
```
21+
22+
心智模型一句話:**模型宣告「哪裡可以量化」(架構事實,寫在 code),config 只做減法
23+
(skip / disable),engine 負責執行並把每筆決策記錄在 checkpoint 裡。**
24+
25+
## 1. 核心概念:決策與執行分離
26+
27+
### 1.1 `QuantRules`:模型的量化宣告(`plan.py`)
28+
29+
每個支援量化的模型有一個 `main_modules/<model>/quantization.py`(現例:PTv3 57 行、
30+
BEVFusion 67 行),核心是:
31+
32+
```python
33+
MODEL_QUANT_RULES = QuantRules(
34+
quantize_submodules={
35+
"backbone": ("conv",), # 全走 config 的 default_precision
36+
"seg3d_head": {"conv": None, "linear": "fp8"}, # per-kind 釘死精度
37+
},
38+
recipes=(...), # 架構 recipe(預設全部;class 比對,不中則不動作)
39+
)
40+
```
41+
42+
- key 是模型**頂層屬性名**;模型沒有該屬性 → 靜默跳過(一份 rules 服務多個變體)。
43+
- 「哪些 kind 可換」是架構事實,屬於 code;config 的 `skip_quantize` /
44+
`disable_recipes` **只能減不能加**
45+
46+
### 1.2 Plan → PlacementRecord:每筆決策可審查
47+
48+
`build_quantization_plan` 把 rules + config 展開成逐模組的 `PlacementDecision`
49+
(哪個模組、換成什麼、**為什麼**),全部進 `PlacementRecord``dry_run` 印的就是它;
50+
quantize 完它內嵌進 checkpoint——這就是「自描述」:deploy/test 讀 record 重建結構,
51+
**不讀 `cfg.quantization`**(same-plan-everywhere 不變量)。
52+
53+
### 1.3 執行端:modelopt registry(`core/`)
54+
55+
模組替換走 modelopt 的 quantized-module registry(`core/modelopt.py`),不自己維護
56+
替換表;校準(`core/calibration.py`)由 config 的 `quantization.calibration` 區塊驅動
57+
(方法/樣本數)。state_dict 的 quantizer key 是 modelopt 慣例(`*input_quantizer.*`)。
58+
59+
### 1.4 Recipes:matcher + action(`recipes/`)
60+
61+
架構特例(如「某類 block 的第二個 conv 跳過」)寫成 recipe:**class 比對**決定在哪
62+
生效、`RECIPE_ATTACHERS` registry 註冊 action;plan import 時驗證 recipe 名,忘記註冊
63+
直接爆。沒有比中任何模組的 recipe 是 no-op(不會誤傷)。
64+
65+
## 2. PTQ workflow(標準路)
66+
67+
1. `dry_run` 確認 placement(§0)。
68+
2. `quantize`:載 FP training ckpt(**未 fuse** 的;BN fusion 由框架在量化前做,見
69+
`core/fusion.py`)→ 替換 → 校準 → 存 `ptq.ckpt`
70+
3. `deploy` 該 ckpt;verification 的 INT8 容差哲學見 deployment README §4。
71+
72+
## 3. QAT workflow(需要時才用)
73+
74+
```yaml
75+
quantization:
76+
mode: qat
77+
qat:
78+
freeze_unquantized: true # 預設;不凍會崩(見下)
79+
schedule: cosine # 或 one_cycle / constant;peak lr 建議 1e-5
80+
```
81+
82+
付過學費的三件事:
83+
84+
1. **`freeze_unquantized: true` 是預設且必要**:未量化層吸收梯度漂移 → 輸出越過
85+
凍結的 amax → clip 歸零梯度 → 正回饋崩潰(CenterPoint 實測 mAP 0.81 → 0.007)。
86+
2. **lr 要小**:freeze + peak 1e-5 整個 epoch 穩定;1e-4 會在 epoch 後段非線性惡化
87+
(300 步探針看不出來,recipe 驗證必須整 epoch)。
88+
3. **QAT 不保證贏 PTQ**:CenterPoint 上兩者持平(0.8128 vs 0.8132),正式路徑是
89+
PTQ;PTv3(attention 模型)QAT 有感(+0.7 mIoU vs PTQ)。先 PTQ,證明不夠再 QAT。
90+
91+
## 4. INT8 vs FP8 怎麼選(兩模型交叉驗證)
92+
93+
| 層類 | 建議 | 證據 |
94+
| --- | --- | --- |
95+
| conv(CNN backbone) | INT8 | CenterPoint/BEVFusion:mAP 損失 <0.02、backbone 3.9→3.5 ms |
96+
| linear(attention/FFN) | **FP8,不要 INT8** | PTv3:INT8 linear 賠 6.4 mIoU,FP8 只賠 0.37;BEVFusion FFN FP8 ±0 |
97+
| attention 內部(QK/AV matmul) | 不量 | 斷 TRT fused-MHA,反而 +0.3 ms |
98+
99+
FP8 走 modelopt 的 trt-domain 自訂 op、per-tensor scale、max 校準;ONNX Runtime
100+
載不了 FP8 圖(該 experiment 關 onnx backend)。完整數據:
101+
`work_dirs/reviews/fp8-quantization-README.md`
102+
103+
## 5. 新增量化模型 checklist
104+
105+
1. **宣告**:寫 `main_modules/<model>/quantization.py`(§1.1),接上模型的
106+
`build_quantization_plan`
107+
2. **config**:`_int8` / `_fp8` experiment(照抄現例改名),含 `skip_quantize` 與
108+
verification scenarios。
109+
3. **dry_run** 看 placement,再 quantize,再 deploy。
110+
4. export log 有 `Quantized chain breaks` 警告 → 處理方式見 deployment README §3 末。
111+
112+
**五個已知陷阱(都付過學費,附實例):**
113+
114+
1. **attention 投影在校準期抓不到**:訓練態 `nn.MultiheadAttention` 的 qkv 是 packed
115+
Parameter,export 態 `q/k/v/out_proj` Linear 在校準之後才誕生;`out_proj` 是
116+
forward 被 fast path 繞過的特殊 Linear(walker 已在框架層拒換)。要量它們 =
117+
校準前先換成 export 態 attention(未實作)。→ 詳:
118+
`models/detection3d/main_modules/bevfusion/quantization.py` docstring。
119+
2. **輸入端層對 INT8 敏感**:吃 raw / scatter 特徵的第一段(CenterPoint stage 0)
120+
量了掉 ~1.2 mAP,release recipe 一直 skip。新模型對輸入段做 leave-one-out。
121+
→ 實例:centerpoint `_int8.yaml` 的 `skip_quantize` 註解。
122+
3. **linear 用 FP8 不用 INT8**(§4)。
123+
4. **ORT 跑不了 plugin stage 與 FP8 op**:前者 `torch_fallback_backends`,後者關
124+
onnx backend。→ 實例:ptv3/base.yaml、bevfusion `_fp8` config。
125+
5. **verification tolerance 實測校準**:量化 stage 的 raw-logit 跨 backend 差是預期,
126+
metric 相等才是 gate;fail 訊息會給建議值。→ 實例:centerpoint `_int8.yaml`。
127+
128+
**一條不可動的地基**:ONNX 圖上 Q/DQ 保持 **fp32-typed(island)**。fp16-typed Q/DQ
129+
(opset 19 合法)踩 TRT 10.8/10.16 缺陷:fp16 合併 scale subnormal → 融合 kernel 產
130+
NaN、build 零警告。island 規則與證據:deployment README §3、
131+
`work_dirs/reviews/fp16-typed-qdq-nogo.md`(金絲雀 = PTv3 INT8 QAT)。
132+
133+
## 6. 檔案地圖
134+
135+
```text
136+
quantization/
137+
plan.py QuantRules / PlacementDecision / build plan(宣告與展開)
138+
config.py quantization config schema(mode/calibration/ptq/qat)
139+
checkpoint.py 自描述 ckpt 的存讀(placement_record 內嵌)
140+
loader.py 按 record 重建量化模型(deploy/test 入口)
141+
qat_callback.py QAT:epoch-0 校準、freeze_unquantized、schedule
142+
core/
143+
modelopt.py modelopt registry 對接(模組替換)
144+
calibration.py 校準執行
145+
fusion.py 量化前 BN fusion
146+
replace.py walker(含 out_proj 等拒換保護)
147+
descriptors.py / quantizer_state.py 模組描述與 quantizer 狀態
148+
recipes/
149+
attach.py RECIPE_ATTACHERS registry
150+
quant_blocks.py recipe 實作(class-matched)
151+
```
152+
153+
## 7. 歷史與深挖
154+
155+
架構是 2026-08~09 從「四種變異機制、三層隱性決策」重構而來;重構診斷、命名決議
156+
(rename 對照表)、21 個 review QA 都在 git history 與
157+
`work_dirs/reviews/`(`migration-framework-review-README.md`、
158+
`ptq-qat-verification-README.md`、`fp8-quantization-README.md`、
159+
`quantization-vs-modelopt-comparison-README.md`、`fp16-typed-qdq-nogo.md`)。想知道「為什麼長這樣」
160+
先查這些,再挖 git log。

0 commit comments

Comments
 (0)