量子化した NanoDet を 1 つ、Qualcomm の 2 デバイスに渡しました。片方はロードすら拒否。もう片方は 324 層すべて NPU で 1.39 ms で走りました。モデルは同一、量子化も同一。違いは、量子化器が触れない float の末尾と、デバイスがそれを許すかどうかでした。
モデル
NanoDet を w8a8 に量子化。キャリブレーションは COCO val2017 の実画像 23 枚(24 枚予定のうち 1 枚のダウンロードが失敗したので、きれいな数字だったとは言いません)。同じグラフを 2 デバイスへ:Dragonwing RB3 Gen 2(QCS6490)と Galaxy S25。
各デバイスの結果
| デバイス | 経路 | 結果 |
|---|---|---|
| RB3 Gen 2 | TFLite、float | 90.0 ms — 352 層:CPU 169、GPU 123、NPU 60。CPU 時間だけで全体の 85% |
| RB3 Gen 2 | TFLite、w8a8 | 6.47 ms(中央値 7.61 ms)— 439 層すべて NPU。同じ経路の float から約 14 倍 |
| RB3 Gen 2 | Precompiled QNN、w8a8 | 拒否 — グラフに量子化できない float 尾部テンソルがある(GFL ヘッド後の分布積分とボックス座標デコード演算)。このデバイスは I/O ごと量子化を要求する |
| Galaxy S25 | Precompiled QNN、w8a8 | 1.39 ms(中央値 1.40 ms)— 324 層すべて NPU |
| Galaxy S25 | Precompiled QNN、float | 1.47 ms — こちらも全層 NPU |
(精度によって層数が違うのは、量子化グラフが余分な量子化演算を含むため。精度をまたいだ比較は層数ではなくレイテンシで。)
同じグラフが二つの運命になる理由
先の実験で YOLOv11-N は RB3 で素直に量子化できました——341 層すべて NPU、5.03 ms。あのエクスポートは生の畳み込み出力だったからです。NanoDet のエクスポートはデコード演算をグラフ内に抱えています。GFL ヘッドの分布積分とボックス座標デコードが畳み込みの後ろで float で走る。量子化器はその末尾を変換できず、RB3 の QNN 経路は I/O が完全に量子化されていないグラフを受け付けません。同じ量子化グラフ、同じデバイス系——片方のモデルの末尾はきれいで、もう片方はそうではない。
S25 は同じグラフを 1.39 ms で走らせます。その QNN 経路は受け入れるからです。そして S25 の float 経路は 1.47 ms——フラッグシップでは量子化はほぼ何も買えません。float 経路がもともと全層 NPU だったからです。RB3 では量子化が 90 ms と 6.47 ms の違いです。float 経路がほぼ CPU だったからです。
RB3 の拒否を直す方法は 2 つ検討中です。量子化モデルを TFLite 経由でコンパイルする(畳み込みは NPU、末尾は CPU)か、エクスポートを分割する(バックボーンとヘッドを純粋な畳み込みとして QNN へ、デコードはホスト側で)。どちらも同じ結論を認めています。デコードの末尾はグラフの中ではなく、ホスト側のものです。
持ち帰るルール
量子化はモデル単体の性質ではなく、グラフとデバイスの I/O 契約の性質です。 「量子化済み」のモデルでも、デバイスが受け付けない float 演算を末尾に抱えていれば、そのデバイスでは走りません。量子化ベンチマークを報告するときは経路とデバイスを書いてください。「w8a8」だけでは、走ったかどうかすら分かりません。
先の 27 倍の話の裏側です。あちらではランタイム経路が速度を決めました。こちらではランタイム経路が「走るかどうか」を決めます。どちらも、決定的な証拠は見出しの数字ではなくレイヤ別プロファイルでした。
モデル自体は AI Hub の external recipe として公開済みです——https://huggingface.co/anguoyang/nanodet ——w8a8 を対応精度に、RB3 の QNN 拒否はレシピの実測レポートに記録してあるので、次に来る人は壁ではなく道標に当たります。