onw 3.2.0: Ternary Bonsai 2 4B / 9B 64-token graphs (one copy of the 2-bit weights), parallel compile in processes, faster kit compiler, paged graphs released under fused ones, vision encoder in the background
Browse files- CHANGELOG.md +16 -0
- CHANGELOG_en.md +31 -0
- README.md +7 -4
- README_en.md +7 -4
- TECHNICAL.md +35 -1
- TECHNICAL_en.md +35 -1
- onw/__init__.py +1 -1
- onw/app.py +9 -4
- onw/chat.py +70 -3
- onw/fused.py +35 -3
- onw/i18n.json +4 -4
- onw/ir.py +8 -0
- onw/npukit/NOTICE.txt +1 -1
- onw/npukit/kit.json +1 -1
- onw/npukit/linux/npukit-linux.tar.xz +2 -2
- onw/npukit/openvino_intel_npu_compiler.dll +2 -2
- onw/npukit/openvino_intel_npu_plugin.dll +2 -2
- onw/npukit/patches/npu_compiler-20e7912.patch +1289 -21
- onw/npukit/patches/openvino-6db412d-npu_plugin.patch +152 -22
- onw/runtime.py +210 -21
- onw/visionjob.py +19 -0
- onw/wired.py +25 -1
- pyproject.toml +1 -1
CHANGELOG.md
CHANGED
|
@@ -5,6 +5,22 @@
|
|
| 5 |
日付は日本時間です。各版は Hugging Face の `ryugyosoft/onw` にタグ(`v0.4.2` 以降)付きで残っています。
|
| 6 |
onw ウィンドウは、新しい版があるとこのファイルから変更点を表示します(`## v…` の見出しで区切っています)。
|
| 7 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
## v3.1.0 (2026-10-19)
|
| 9 |
- **Qwen 系を速く(設定「Qwen 系を高速化する」、既定オン)**:Qwen3.5・Ornith など、これまで NPUW で動いていたモデルを、Lunar Lake 以降の PC(onw の NPU キット)では区間ごとの融合グラフと MTP 先読みで動かします。重みは 1 組のまま(キットの共有)で、同じ質問には 3.0.0 と同じ答えが返ります。
|
| 10 |
- Panther Lake(Core Ultra X7 358H)の Qwen3.5-9B NF4、3.0.0 との比較:約 3.5K トークンのプロンプト 205 → 299 tok/s、約 900 トークン 242 → 280 tok/s、生成 17.9 → 19.3 tok/s。
|
|
|
|
| 5 |
日付は日本時間です。各版は Hugging Face の `ryugyosoft/onw` にタグ(`v0.4.2` 以降)付きで残っています。
|
| 6 |
onw ウィンドウは、新しい版があるとこのファイルから変更点を表示します(`## v…` の見出しで区切っています)。
|
| 7 |
|
| 8 |
+
## v3.2.0 (2026-10-20)
|
| 9 |
+
- **Ternary Bonsai 2 4B/9B のプロンプト処理を 10 倍以上に(64 トークン用グラフ、2 ビットの重みは 1 組のまま)**:これまで 2 ビットの重みは 1 トークンずつしか正しく計算できず、プロンプトも 1 トークンずつでした。onw の NPU キットのコンパイラーで 64 トークンのブロックを正しく速く計算できるようにし、NPU 上の 2 ビットの重みを 1 トークン用と 64 トークン用のグラフで共有します(重みを 2 組持たない)。モデルの更新(64 トークン用のグラフ入り)が必要です(モデルの一覧に「更新」が出ます。新しいグラフは onw 3.2 以降だけが使い、3.1.0 以前はこれまでどおり 1 トークンずつ動きます)。Panther Lake(Core Ultra X7 358H)、3.1.0 との比較:
|
| 10 |
+
- Bonsai 2 4B:約 1,200 トークンのプロンプト 25 → 約 300 tok/s、生成 20〜21.6 → 約 22 tok/s、専用メモリ 2.96 → 約 2.1 GB、初回のロード 約 3.6 → 約 6 分(2 回目以降 約 20 秒)。
|
| 11 |
+
- Bonsai 2 9B:プロンプト 約 190 tok/s、生成 約 14.5 tok/s、専用メモリ 約 3.2 GB、初回のロード 約 12 分(2 回目以降 約 35 秒)。
|
| 12 |
+
- 画像の質問も、64 トークンのブロックで読み込みます。
|
| 13 |
+
- **正直に書くと、NPU のメモリは 2 ビットの重みよりずっと大きく、ここは失敗です**:重みとスケール 約 0.9 / 2.1 GB に対してNPU 約 2.6 / 4.5 GB(4B / 9B)。グラフごとに要る重みテーブルが、1 トークン用と 64 トークン用の 2 組で重みと同じくらいあるためです。Intel に改善を要望しています。
|
| 14 |
+
- **Ternary Bonsai 2 27B は 1 トークン用のグラフのままです(64 トークン用は試作したが失敗)**:作って動かすことはできました(プロンプト 約 60 tok/s)が、NPU のメモリを 13.8 GB 使い、16 GB の PC に載りません。2 ビットの重みを NPU で計算するには、グループ(128 個の重み)ごとに 16 バイトの「重みテーブル」がグラフごとに必要で、27B ではグラフ 1 組あたり 3.4 GB になるためです。初回のコンパイルも Panther Lake で 36 分かかりました。Intel に改善を要望しています。
|
| 15 |
+
- **初回のロードを速く:コンパイルを複数のプロセスで同時に**:NPU のコンパイラーはグラフ 1 つに CPU を約 1 コアしか使わないので、空きメモリとコア数に応じて最大 6 つの子プロセスで同時にコンパイルします(省メモリモードでは最大 2)。子プロセスはコンパイルして保存するだけで、NPU には読み込みません(重みが二重にならない)。Panther Lake で Bonsai 2 4B の 1 トークン用+64 トークン用のグラフ:819 → 約 180 秒。融合グラフの裏でのコンパイルも、既定で同時に行います。
|
| 16 |
+
- **NPU コンパイラーの高速化(キット)**:戦略探索で同じ計算を繰り返していた所を直し、2 ビットのモデルの 64 トークン用グラフ 1 つのコンパイルが 335 → 約 20 秒になりました(結果のバイナリーは同じ)。
|
| 17 |
+
- **融合グラフを使う大きさでは、従来のグラフをメモリから外す**:融合グラフが動く範囲では従来のグラフは使わないので、読み込んだあとで外します(重みテーブルの分だけ省メモリ。Bonsai 2 4B:NPU 3.9 → 2.6 GB)。画像の質問や、融合グラフより長い文脈のときだけ、その場で読み込み直します(数秒)。
|
| 18 |
+
- **画像エンコーダーを裏でコンパイル**:画像の付いたモデルは、初回のロードのあと裏で画像エンコーダーをコンパイルしておき、最初の画像の質問で数分待たないようにしました。
|
| 19 |
+
- **Linux 版のキットも更新**しました(上の修正を含む。Bonsai 2 の 64 トークン用グラフも、Ubuntu の Lunar Lake で確かめました)。
|
| 20 |
+
- 2 ビットのモデルでは、融合グラフの長さ違いの種類を全部は常駐させず、1 つずつ入れ替えます(種類ごとに重みテーブルが要るため)。
|
| 21 |
+
- 融合グラフでも、最後の正規化の fp16 のあふれで 0 になった行を、縮めて計算し直します(従来のグラフと同じ処理。Bonsai 2 9B の 64 トークンのブロックで、Lunar Lake の 256 行中 20 行が一様分布になっていた)。
|
| 22 |
+
- 2 ビットのモデルでは、先読み検証(PLD)を使いません(下書きの確認に 64 トークン用のグラフを使うこ��になり、Lunar Lake で生成が 23 → 13〜17 tok/s に落ちていた)。
|
| 23 |
+
|
| 24 |
## v3.1.0 (2026-10-19)
|
| 25 |
- **Qwen 系を速く(設定「Qwen 系を高速化する」、既定オン)**:Qwen3.5・Ornith など、これまで NPUW で動いていたモデルを、Lunar Lake 以降の PC(onw の NPU キット)では区間ごとの融合グラフと MTP 先読みで動かします。重みは 1 組のまま(キットの共有)で、同じ質問には 3.0.0 と同じ答えが返ります。
|
| 26 |
- Panther Lake(Core Ultra X7 358H)の Qwen3.5-9B NF4、3.0.0 との比較:約 3.5K トークンのプロンプト 205 → 299 tok/s、約 900 トークン 242 → 280 tok/s、生成 17.9 → 19.3 tok/s。
|
CHANGELOG_en.md
CHANGED
|
@@ -4,6 +4,37 @@
|
|
| 4 |
|
| 5 |
Dates are JST. Every version is on Hugging Face `ryugyosoft/onw`, tagged from `v0.4.2` on.
|
| 6 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
## v3.1.0 (2026-10-19)
|
| 8 |
- **The Qwen family faster (setting "Speed up the Qwen family", on by default)**: Qwen3.5, Ornith and the other models that ran through NPUW run fused segment graphs with MTP draft tokens on PCs from Lunar Lake on (onw's NPU kit). One copy of the weights as before (the kit shares them), and the same question gets the same answer as with 3.0.0.
|
| 9 |
- Panther Lake (Core Ultra X7 358H), Qwen3.5-9B NF4 against 3.0.0: a ~3.5K-token prompt 205 -> 299 tok/s, ~900 tokens 242 -> 280 tok/s, answers 17.9 -> 19.3 tok/s.
|
|
|
|
| 4 |
|
| 5 |
Dates are JST. Every version is on Hugging Face `ryugyosoft/onw`, tagged from `v0.4.2` on.
|
| 6 |
|
| 7 |
+
## v3.2.0 (2026-10-20)
|
| 8 |
+
- **Ternary Bonsai 2 4B / 9B: prompts more than 10x faster (64-token graphs, the 2-bit weights still held once)**:
|
| 9 |
+
until now 2-bit weights were only computed right one token at a time, so prompts went one token at a time too. The
|
| 10 |
+
compiler of onw's NPU kit now computes 64-token blocks right and fast, and the 1-token and the 64-token graphs share
|
| 11 |
+
the 2-bit weights on the NPU (no second copy). Needs the updated models (with 64-token graphs; the model list offers the update. Only onw 3.2 and later use the new graphs - 3.1.0 and earlier keep going token by token). Panther Lake
|
| 12 |
+
(Core Ultra X7 358H), against 3.1.0:
|
| 13 |
+
- Bonsai 2 4B: ~1,200-token prompt 25 -> ~300 tok/s, answers 20-21.6 -> ~22 tok/s, private memory 2.96 -> ~2.1 GB,
|
| 14 |
+
first load ~3.6 -> ~6 min (later loads ~20 s).
|
| 15 |
+
- Bonsai 2 9B: prompts ~190 tok/s, answers ~14.5 tok/s, private memory ~3.2 GB, first load ~12 min (later ~35 s).
|
| 16 |
+
- Image questions are read in 64-token blocks too.
|
| 17 |
+
- **To be frank, the NPU memory is far above the 2-bit weights - a failure on that count**: weights + scales ~0.9 / 2.1 GB, NPU ~2.6 / 4.5 GB (4B / 9B). The weight tables every graph needs are, for the 1-token and the 64-token set, about as large as the weights. Requested from Intel.
|
| 18 |
+
- **Ternary Bonsai 2 27B keeps its 1-token graphs (64-token graphs prototyped - and failed)**: they built and ran
|
| 19 |
+
(prompts ~60 tok/s), but used 13.8 GB of NPU memory - no 16 GB PC holds that. Computing 2-bit weights on the NPU needs
|
| 20 |
+
a 16-byte "weight table" entry per group of 128 weights in every graph, 3.4 GB per set of graphs for the 27B. The first
|
| 21 |
+
compile also took 36 minutes on Panther Lake. Reported to Intel with a request.
|
| 22 |
+
- **Faster first loads: graphs compile in parallel processes**: the NPU compiler uses about one CPU core per graph, so up
|
| 23 |
+
to 6 child processes compile at the same time, as the free memory and the cores allow (2 at most in the memory-saving
|
| 24 |
+
mode). The children only compile and save; nothing is loaded on the NPU twice. Panther Lake, Bonsai 2 4B 1-token +
|
| 25 |
+
64-token graphs: 819 -> ~180 s. The fused graphs' background compile runs in parallel by default as well.
|
| 26 |
+
- **Faster NPU compiler (kit)**: the strategy search no longer repeats identical work; one 64-token graph of a 2-bit model
|
| 27 |
+
compiles in ~20 instead of 335 s (same binary).
|
| 28 |
+
- **The paged graphs leave memory where the fused graphs run**: they are not used for the positions the fused graphs
|
| 29 |
+
cover, so they are released after loading (their weight tables: Bonsai 2 4B NPU memory 3.9 -> 2.6 GB). Image questions
|
| 30 |
+
and contexts longer than the fused graphs load them again on the spot (a few seconds).
|
| 31 |
+
- **The vision encoder compiles in the background**: models that read images compile their encoder right after the
|
| 32 |
+
first load, so the first image question does not wait minutes.
|
| 33 |
+
- **The Linux kit is updated** too (with the fixes above; Bonsai 2's 64-token graphs checked on Lunar Lake under Ubuntu too).
|
| 34 |
+
- 2-bit models keep one fused variant at a time instead of all lengths (each variant needs its own weight tables).
|
| 35 |
+
- The fused graphs recompute rows the final norm's fp16 overflow zeroed, scaled down (as the paged graphs do; Bonsai 2 9B 64-token blocks on Lunar Lake: 20 of 256 rows came out uniform).
|
| 36 |
+
- 2-bit models do not use prompt-lookup decoding (a draft check would run the 64-token graphs: answers fell from 23 to 13-17 tok/s on Lunar Lake).
|
| 37 |
+
|
| 38 |
## v3.1.0 (2026-10-19)
|
| 39 |
- **The Qwen family faster (setting "Speed up the Qwen family", on by default)**: Qwen3.5, Ornith and the other models that ran through NPUW run fused segment graphs with MTP draft tokens on PCs from Lunar Lake on (onw's NPU kit). One copy of the weights as before (the kit shares them), and the same question gets the same answer as with 3.0.0.
|
| 40 |
- Panther Lake (Core Ultra X7 358H), Qwen3.5-9B NF4 against 3.0.0: a ~3.5K-token prompt 205 -> 299 tok/s, ~900 tokens 242 -> 280 tok/s, answers 17.9 -> 19.3 tok/s.
|
README.md
CHANGED
|
@@ -105,8 +105,8 @@ Python や git の知識は要りません。Python が入っていなければ
|
|
| 105 |
| [ryugyosoft/Ornith-1.5-9B-onw](https://huggingface.co/ryugyosoft/Ornith-1.5-9B-onw) | `qwen3_5`(Qwen3.5-9B をエージェント・ツール呼び出し向けに鍛えた Ornith-1.5) | テキスト+画像 | 切替 | 4.9 tok/s | 5.3 GB | 約 11 GB |
|
| 106 |
| [ryugyosoft/Ornith-1.5-9B-nf4-onw](https://huggingface.co/ryugyosoft/Ornith-1.5-9B-nf4-onw) | 同上、重みはチャネル単位 NF4、MTP 先読み付き(**Lunar Lake 以降専用**) | テキスト+画像 | 切替 | —(Lunar Lake で約 15 tok/s、通常版の約 2 倍) | 5.6 GB | 通常版の約 85% |
|
| 107 |
| [ryugyosoft/Ternary-Bonsai-2-27B-onw](https://huggingface.co/ryugyosoft/Ternary-Bonsai-2-27B-onw) | `qwen3_5`(Qwen3.8-27B を三値の重みで学習し直した Ternary Bonsai 2。重みは 2 ビットのまま。**Lunar Lake 以降専用**) | テキスト | 切替(既定オフ) | —(Lunar Lake で約 4 tok/s。プロンプトは 1 トークンずつ) | 7.6 GB | 約 10 GB |
|
| 108 |
-
| [ryugyosoft/Ternary-Bonsai-2-9B-onw](https://huggingface.co/ryugyosoft/Ternary-Bonsai-2-9B-onw) | `qwen3_5`(Qwen3.5-9B を三値の重みで学習し直した Ternary Bonsai 2。重みは 2 ビットのまま。**Lunar Lake 以降専用**) | テキスト+画像 | 切替 | —(Lunar Lake で約 13 tok/s。プロンプトは
|
| 109 |
-
| [ryugyosoft/Ternary-Bonsai-2-4B-onw](https://huggingface.co/ryugyosoft/Ternary-Bonsai-2-4B-onw) | `qwen3_5`(同じく Qwen3.5-4B の三値版。**Lunar Lake 以降専用**) | テキスト+画像 | 切替 | —(Lunar Lake で約 21 tok/s。プロンプトは
|
| 110 |
| [ryugyosoft/NeoHorse-1-4B-onw](https://huggingface.co/ryugyosoft/NeoHorse-1-4B-onw) | `qwen3_5`(Qwen3.5-4B をエージェント・ツール呼び出し向けに追加学習した NeoHorse-1) | テキスト | 切替 | 6.4 tok/s | 2.4 GB | 約 6 GB |
|
| 111 |
| [ryugyosoft/granite-4.2-3b-onw](https://huggingface.co/ryugyosoft/granite-4.2-3b-onw) | `granite`(IBM Granite 4.2:思考とツール呼び出しを強化した 3B。Llama 型) | テキスト | 切替 | 6.7 tok/s(チャット約 7.7) | 2.1 GB | 約 6 GB |
|
| 112 |
| [ryugyosoft/granite-4.2-8b-onw](https://huggingface.co/ryugyosoft/granite-4.2-8b-onw) | `granite`(同上の 8B) | テキスト | 切替 | 3.6 tok/s(チャット約 3.9) | 5.0 GB | 約 13 GB |
|
|
@@ -128,7 +128,7 @@ down_proj だけチャネル単位 INT8。INT4 版より元のモデルに近く
|
|
| 128 |
|
| 129 |
**2 ビット版(onw 1.4.9、Lunar Lake 以降専用)**:Lunar Lake 以降の NPU は 2 ビットの重みも計算できます。Ternary Bonsai 2 27B は三値(−1・0・+1)で
|
| 130 |
学習されたモデルで、重みを 2 ビットのまま NPU に載せると 27B がメモリ約 10 GB で動きます。ただし今の NPU コンパイラーは 2 ビットの重みを複数トークンの
|
| 131 |
-
ブロックで計算すると間違えるため、プロンプトは 1 トークンずつ(約 4 tok/s)処理します。実用というより「NPU で 27B が動く」ロマン向けです(Intel に報告済み)。onw 1.4.13 では同じ三値の Ternary Bonsai 2 9B / 4B も加えました。こちらは画像も読め、メモリ約 3.5 GB / 約 2 GB と軽く、Lunar Lake で生成約 13 / 21 tok/s です(プロンプト
|
| 132 |
|
| 133 |
**MTP 先読み(onw 1.4.0、Lunar Lake 以降)**:Qwen3.5 系のモデルには、次の次のト��クンを予想する小さな層(MTP)が付いています。
|
| 134 |
onw はこれで 2 トークン先まで下書きし、本体の 4 トークン用のグラフで一度に確かめます(外れた下書きの分だけ正確に巻き戻します)。
|
|
@@ -328,7 +328,10 @@ KV キャッシュは INT8 で、会話が実際に使った長さの分だけ
|
|
| 328 |
(Windows 11、ドライバー 32.0.100.5540、2026 年 10 月 6 日。1 パーツは 4 トークン用グラフの最初のパーツ。命令の大きさは
|
| 329 |
コンパイル結果の ELF セクションの大きさで、命令の数そのものではありません。)
|
| 330 |
|
| 331 |
-
### 既知の問題(onw 3.
|
|
|
|
|
|
|
|
|
|
| 332 |
|
| 333 |
- **Qwen 系の高速化をオンにした最初のロードは長め**:融合グラフのコンパイルが加わります(Panther Lake の小型機の Qwen3.5-9B NF4:使い始めるまで約 13 分、残りの種類は回答しながら裏で約 25 分)。2 回目以降はキャッシュから数十秒です。
|
| 334 |
- **INT4 版のモデルと Meteor Lake・Arrow Lake(NPU 3720)は 2.1.0 と同じです**:INT4 の重みは重みを持たないグラフにできず、NPU 3720 は
|
|
|
|
| 105 |
| [ryugyosoft/Ornith-1.5-9B-onw](https://huggingface.co/ryugyosoft/Ornith-1.5-9B-onw) | `qwen3_5`(Qwen3.5-9B をエージェント・ツール呼び出し向けに鍛えた Ornith-1.5) | テキスト+画像 | 切替 | 4.9 tok/s | 5.3 GB | 約 11 GB |
|
| 106 |
| [ryugyosoft/Ornith-1.5-9B-nf4-onw](https://huggingface.co/ryugyosoft/Ornith-1.5-9B-nf4-onw) | 同上、重みはチャネル単位 NF4、MTP 先読み付き(**Lunar Lake 以降専用**) | テキスト+画像 | 切替 | —(Lunar Lake で約 15 tok/s、通常版の約 2 倍) | 5.6 GB | 通常版の約 85% |
|
| 107 |
| [ryugyosoft/Ternary-Bonsai-2-27B-onw](https://huggingface.co/ryugyosoft/Ternary-Bonsai-2-27B-onw) | `qwen3_5`(Qwen3.8-27B を三値の重みで学習し直した Ternary Bonsai 2。重みは 2 ビットのまま。**Lunar Lake 以降専用**) | テキスト | 切替(既定オフ) | —(Lunar Lake で約 4 tok/s。プロンプトは 1 トークンずつ) | 7.6 GB | 約 10 GB |
|
| 108 |
+
| [ryugyosoft/Ternary-Bonsai-2-9B-onw](https://huggingface.co/ryugyosoft/Ternary-Bonsai-2-9B-onw) | `qwen3_5`(Qwen3.5-9B を三値の重みで学習し直した Ternary Bonsai 2。重みは 2 ビットのまま。**Lunar Lake 以降専用**) | テキスト+画像 | 切替 | —(Lunar Lake で約 13 tok/s。3.2.0 からプロンプトは 64 トークンずつ) | 3.0 GB | 約 3.5 GB |
|
| 109 |
+
| [ryugyosoft/Ternary-Bonsai-2-4B-onw](https://huggingface.co/ryugyosoft/Ternary-Bonsai-2-4B-onw) | `qwen3_5`(同じく Qwen3.5-4B の三値版。**Lunar Lake 以降専用**) | テキスト+画像 | 切替 | —(Lunar Lake で約 21 tok/s。3.2.0 からプロンプトは 64 トークンずつ) | 1.8 GB | 約 2 GB |
|
| 110 |
| [ryugyosoft/NeoHorse-1-4B-onw](https://huggingface.co/ryugyosoft/NeoHorse-1-4B-onw) | `qwen3_5`(Qwen3.5-4B をエージェント・ツール呼び出し向けに追加学習した NeoHorse-1) | テキスト | 切替 | 6.4 tok/s | 2.4 GB | 約 6 GB |
|
| 111 |
| [ryugyosoft/granite-4.2-3b-onw](https://huggingface.co/ryugyosoft/granite-4.2-3b-onw) | `granite`(IBM Granite 4.2:思考とツール呼び出しを強化した 3B。Llama 型) | テキスト | 切替 | 6.7 tok/s(チャット約 7.7) | 2.1 GB | 約 6 GB |
|
| 112 |
| [ryugyosoft/granite-4.2-8b-onw](https://huggingface.co/ryugyosoft/granite-4.2-8b-onw) | `granite`(同上の 8B) | テキスト | 切替 | 3.6 tok/s(チャット約 3.9) | 5.0 GB | 約 13 GB |
|
|
|
|
| 128 |
|
| 129 |
**2 ビット版(onw 1.4.9、Lunar Lake 以降専用)**:Lunar Lake 以降の NPU は 2 ビットの重みも計算できます。Ternary Bonsai 2 27B は三値(−1・0・+1)で
|
| 130 |
学習されたモデルで、重みを 2 ビットのまま NPU に載せると 27B がメモリ約 10 GB で動きます。ただし今の NPU コンパイラーは 2 ビットの重みを複数トークンの
|
| 131 |
+
ブロックで計算すると間違えるため、プロンプトは 1 トークンずつ(約 4 tok/s)処理します。実用というより「NPU で 27B が動く」ロマン向けです(Intel に報告済み)。onw 1.4.13 では同じ三値の Ternary Bonsai 2 9B / 4B も加えました。こちらは画像も読め、メモリ約 3.5 GB / 約 2 GB と軽く、Lunar Lake で生成約 13 / 21 tok/s です。onw 3.2.0 では、NPU キットのコンパイラーでこの不具合を直し、9B / 4B に 64 トークン用のグラフを加えました(2 ビットの重みは 1 組のまま共有。Panther Lake でプロンプト約 190 / 300 tok/s)。27B も試作しましたが、NPU のメモリを 13.8 GB 使うため出していません(1 トークンずつのまま)。
|
| 132 |
|
| 133 |
**MTP 先読み(onw 1.4.0、Lunar Lake 以降)**:Qwen3.5 系のモデルには、次の次のト��クンを予想する小さな層(MTP)が付いています。
|
| 134 |
onw はこれで 2 トークン先まで下書きし、本体の 4 トークン用のグラフで一度に確かめます(外れた下書きの分だけ正確に巻き戻します)。
|
|
|
|
| 328 |
(Windows 11、ドライバー 32.0.100.5540、2026 年 10 月 6 日。1 パーツは 4 トークン用グラフの最初のパーツ。命令の大きさは
|
| 329 |
コンパイル結果の ELF セクションの大きさで、命令の数そのものではありません。)
|
| 330 |
|
| 331 |
+
### 既知の問題(onw 3.2.0、2026 年 10 月 20 日)
|
| 332 |
+
|
| 333 |
+
- **Ternary Bonsai 2 27B の 64 トークン用グラフは試作したが失敗**:NPU のメモリを 13.8 GB 使い(グラフごとに要る 2 ビットの重みテーブルが 1 組 3.4 GB)、16 GB の PC に載らないため出していません。27B のプロンプトは 1 トークンずつのままです。Intel に要望しています。
|
| 334 |
+
- **Ternary Bonsai 2 の 64 トークン版の初回のロードは長め**:Panther Lake で 4B 約 6 分、9B 約 12 分(2 回目以降は 20〜35 秒)。
|
| 335 |
|
| 336 |
- **Qwen 系の高速化をオンにした最初のロードは長め**:融合グラフのコンパイルが加わります(Panther Lake の小型機の Qwen3.5-9B NF4:使い始めるまで約 13 分、残りの種類は回答しながら裏で約 25 分)。2 回目以降はキャッシュから数十秒です。
|
| 337 |
- **INT4 版のモデルと Meteor Lake・Arrow Lake(NPU 3720)は 2.1.0 と同じです**:INT4 の重みは重みを持たないグラフにできず、NPU 3720 は
|
README_en.md
CHANGED
|
@@ -92,8 +92,8 @@ troubleshooting), is the **[user guide](MANUAL_en.md)**.
|
|
| 92 |
| [ryugyosoft/Ornith-1.5-9B-onw](https://huggingface.co/ryugyosoft/Ornith-1.5-9B-onw) | `qwen3_5` (Ornith-1.5: Qwen3.5-9B trained further for agents and tool calls) | text + image | switchable | 4.9 tok/s | 5.3 GB | ~11 GB |
|
| 93 |
| [ryugyosoft/Ornith-1.5-9B-nf4-onw](https://huggingface.co/ryugyosoft/Ornith-1.5-9B-nf4-onw) | the same, channel-wise NF4 weights, MTP draft tokens (**Lunar Lake and newer only**) | text + image | switchable | - (~15 tok/s on Lunar Lake, ~2x the regular build) | 5.6 GB | ~85% of the regular build |
|
| 94 |
| [ryugyosoft/Ternary-Bonsai-2-27B-onw](https://huggingface.co/ryugyosoft/Ternary-Bonsai-2-27B-onw) | `qwen3_5` (Ternary Bonsai 2: Qwen3.8-27B retrained with ternary weights, kept 2-bit; **Lunar Lake and newer only**) | text | switchable (off by default) | - (~4 tok/s on Lunar Lake; prompts token by token) | 7.6 GB | ~10 GB |
|
| 95 |
-
| [ryugyosoft/Ternary-Bonsai-2-9B-onw](https://huggingface.co/ryugyosoft/Ternary-Bonsai-2-9B-onw) | `qwen3_5` (Ternary Bonsai 2: Qwen3.5-9B retrained with ternary weights, kept 2-bit; **Lunar Lake and newer only**) | text + image | switchable | - (~13 tok/s on Lunar Lake; prompts
|
| 96 |
-
| [ryugyosoft/Ternary-Bonsai-2-4B-onw](https://huggingface.co/ryugyosoft/Ternary-Bonsai-2-4B-onw) | `qwen3_5` (the same for Qwen3.5-4B; **Lunar Lake and newer only**) | text + image | switchable | - (~21 tok/s on Lunar Lake; prompts
|
| 97 |
| [ryugyosoft/NeoHorse-1-4B-onw](https://huggingface.co/ryugyosoft/NeoHorse-1-4B-onw) | `qwen3_5` (NeoHorse-1: Qwen3.5-4B trained further for agents and tool calls) | text | switchable | 6.4 tok/s | 2.4 GB | ~6 GB |
|
| 98 |
| [ryugyosoft/granite-4.2-3b-onw](https://huggingface.co/ryugyosoft/granite-4.2-3b-onw) | `granite` (IBM Granite 4.2: a 3B with thinking and stronger tool calls; Llama-type) | text | switchable | 6.7 tok/s (~7.7 in chat) | 2.1 GB | ~6 GB |
|
| 99 |
| [ryugyosoft/granite-4.2-8b-onw](https://huggingface.co/ryugyosoft/granite-4.2-8b-onw) | `granite` (the same, 8B) | text | switchable | 3.6 tok/s (~3.9 in chat) | 5.0 GB | ~13 GB |
|
|
@@ -117,7 +117,7 @@ original than the INT4 builds; on Panther Lake Granite 4.2 8B decodes ~1.7x as f
|
|
| 117 |
**2-bit build (onw 1.4.9, Lunar Lake and newer only)**: NPUs from Lunar Lake on compute 2-bit weights too. Ternary Bonsai 2 27B was
|
| 118 |
trained with ternary weights (-1, 0, +1); kept 2-bit on the NPU, the 27B runs in about 10 GB of memory. Today's NPU compilers compute
|
| 119 |
2-bit weights wrong in blocks of more than one token, so prompts are processed token by token (~4 tok/s): a "27B on the NPU" showpiece
|
| 120 |
-
rather than a daily driver (reported to Intel). onw 1.4.13 adds the ternary Ternary Bonsai 2 9B / 4B as well: they read images, need only ~3.5 / ~2 GB of memory and generate ~13 / ~21 tok/s on Lunar Lake (prompts token by token
|
| 121 |
|
| 122 |
**MTP draft tokens (onw 1.4.0, Lunar Lake and newer)**: Qwen3.5-family models carry a small layer (MTP) that predicts the
|
| 123 |
token after next. onw uses it to draft two tokens ahead and checks them in one pass on the model's 4-token graphs
|
|
@@ -316,7 +316,10 @@ How it was measured, and caveats:
|
|
| 316 |
(Windows 11, driver 32.0.100.5540, 2026-10-06. One part = the first part of a 4-token graph. Code sizes are ELF section sizes of
|
| 317 |
the compiled file, not instruction counts.)
|
| 318 |
|
| 319 |
-
### Known issues (onw 3.
|
|
|
|
|
|
|
|
|
|
| 320 |
|
| 321 |
- **The first load with the Qwen family sped up takes longer**: the fused graphs compile as well (a small Panther Lake PC, Qwen3.5-9B NF4: ~13 min until usable, the other variants ~25 min in the background while you chat). Later loads come from the cache in well under a minute.
|
| 322 |
- **The INT4 builds and Meteor Lake / Arrow Lake (NPU 3720) are as in 2.1.0**: INT4 weights cannot go into graphs without weights,
|
|
|
|
| 92 |
| [ryugyosoft/Ornith-1.5-9B-onw](https://huggingface.co/ryugyosoft/Ornith-1.5-9B-onw) | `qwen3_5` (Ornith-1.5: Qwen3.5-9B trained further for agents and tool calls) | text + image | switchable | 4.9 tok/s | 5.3 GB | ~11 GB |
|
| 93 |
| [ryugyosoft/Ornith-1.5-9B-nf4-onw](https://huggingface.co/ryugyosoft/Ornith-1.5-9B-nf4-onw) | the same, channel-wise NF4 weights, MTP draft tokens (**Lunar Lake and newer only**) | text + image | switchable | - (~15 tok/s on Lunar Lake, ~2x the regular build) | 5.6 GB | ~85% of the regular build |
|
| 94 |
| [ryugyosoft/Ternary-Bonsai-2-27B-onw](https://huggingface.co/ryugyosoft/Ternary-Bonsai-2-27B-onw) | `qwen3_5` (Ternary Bonsai 2: Qwen3.8-27B retrained with ternary weights, kept 2-bit; **Lunar Lake and newer only**) | text | switchable (off by default) | - (~4 tok/s on Lunar Lake; prompts token by token) | 7.6 GB | ~10 GB |
|
| 95 |
+
| [ryugyosoft/Ternary-Bonsai-2-9B-onw](https://huggingface.co/ryugyosoft/Ternary-Bonsai-2-9B-onw) | `qwen3_5` (Ternary Bonsai 2: Qwen3.5-9B retrained with ternary weights, kept 2-bit; **Lunar Lake and newer only**) | text + image | switchable | - (~13 tok/s on Lunar Lake; prompts 64 tokens at a time from 3.2.0) | 3.0 GB | ~3.5 GB |
|
| 96 |
+
| [ryugyosoft/Ternary-Bonsai-2-4B-onw](https://huggingface.co/ryugyosoft/Ternary-Bonsai-2-4B-onw) | `qwen3_5` (the same for Qwen3.5-4B; **Lunar Lake and newer only**) | text + image | switchable | - (~21 tok/s on Lunar Lake; prompts 64 tokens at a time from 3.2.0) | 1.8 GB | ~2 GB |
|
| 97 |
| [ryugyosoft/NeoHorse-1-4B-onw](https://huggingface.co/ryugyosoft/NeoHorse-1-4B-onw) | `qwen3_5` (NeoHorse-1: Qwen3.5-4B trained further for agents and tool calls) | text | switchable | 6.4 tok/s | 2.4 GB | ~6 GB |
|
| 98 |
| [ryugyosoft/granite-4.2-3b-onw](https://huggingface.co/ryugyosoft/granite-4.2-3b-onw) | `granite` (IBM Granite 4.2: a 3B with thinking and stronger tool calls; Llama-type) | text | switchable | 6.7 tok/s (~7.7 in chat) | 2.1 GB | ~6 GB |
|
| 99 |
| [ryugyosoft/granite-4.2-8b-onw](https://huggingface.co/ryugyosoft/granite-4.2-8b-onw) | `granite` (the same, 8B) | text | switchable | 3.6 tok/s (~3.9 in chat) | 5.0 GB | ~13 GB |
|
|
|
|
| 117 |
**2-bit build (onw 1.4.9, Lunar Lake and newer only)**: NPUs from Lunar Lake on compute 2-bit weights too. Ternary Bonsai 2 27B was
|
| 118 |
trained with ternary weights (-1, 0, +1); kept 2-bit on the NPU, the 27B runs in about 10 GB of memory. Today's NPU compilers compute
|
| 119 |
2-bit weights wrong in blocks of more than one token, so prompts are processed token by token (~4 tok/s): a "27B on the NPU" showpiece
|
| 120 |
+
rather than a daily driver (reported to Intel). onw 1.4.13 adds the ternary Ternary Bonsai 2 9B / 4B as well: they read images, need only ~3.5 / ~2 GB of memory and generate ~13 / ~21 tok/s on Lunar Lake. onw 3.2.0 fixes the compiler bug in the NPU kit and adds 64-token graphs to the 9B / 4B (sharing one copy of the 2-bit weights; prompts ~190 / ~300 tok/s on Panther Lake). We prototyped them for the 27B too, but it took 13.8 GB of NPU memory, so the 27B stays token by token.
|
| 121 |
|
| 122 |
**MTP draft tokens (onw 1.4.0, Lunar Lake and newer)**: Qwen3.5-family models carry a small layer (MTP) that predicts the
|
| 123 |
token after next. onw uses it to draft two tokens ahead and checks them in one pass on the model's 4-token graphs
|
|
|
|
| 316 |
(Windows 11, driver 32.0.100.5540, 2026-10-06. One part = the first part of a 4-token graph. Code sizes are ELF section sizes of
|
| 317 |
the compiled file, not instruction counts.)
|
| 318 |
|
| 319 |
+
### Known issues (onw 3.2.0, 2026-10-20)
|
| 320 |
+
|
| 321 |
+
- **64-token graphs for Ternary Bonsai 2 27B: prototyped, and failed**: they took 13.8 GB of NPU memory (the 2-bit weight tables every graph needs are 3.4 GB per set), which no 16 GB PC holds, so they are not shipped. The 27B still reads prompts token by token. Requested from Intel.
|
| 322 |
+
- **The first load of the 64-token Ternary Bonsai 2 builds takes a while**: on Panther Lake ~6 min for the 4B, ~12 min for the 9B (later loads 20-35 s).
|
| 323 |
|
| 324 |
- **The first load with the Qwen family sped up takes longer**: the fused graphs compile as well (a small Panther Lake PC, Qwen3.5-9B NF4: ~13 min until usable, the other variants ~25 min in the background while you chat). Later loads come from the cache in well under a minute.
|
| 325 |
- **The INT4 builds and Meteor Lake / Arrow Lake (NPU 3720) are as in 2.1.0**: INT4 weights cannot go into graphs without weights,
|
TECHNICAL.md
CHANGED
|
@@ -59,7 +59,7 @@ OpenVINO 2026.5 のコンパイラーは NF4 の重みでこれに失敗し、
|
|
| 59 |
63.5 対 63.6 MB)。INT4 は 2 行以上のグラフで、NPU 4000 以降のコンパイラーが INT4 を表引き(4 ビットの値をそのまま表の番号に
|
| 60 |
する)に変える処理と重なって失敗し、直してもコンパイルが 20〜35 倍遅くなるだけでした。このため 2 ビットと INT4 のモデル
|
| 61 |
(NPUW を使わないもの)では使わず、融合グラフも既定でオフです(2.1.0 と同じ動作)。INT8 は分離されます
|
| 62 |
-
(llm-jp-4.1-8B INT8 の部品で 2.5 対 216 MB)。Lunar Lake 以降では NF4 版を勧めます。
|
| 63 |
|
| 64 |
### コンパイラーの修正(npu_compiler 8.3 をもとに)
|
| 65 |
|
|
@@ -139,6 +139,40 @@ OpenVINO の仕組み)で動かしていました。キットで重みを共
|
|
| 139 |
|
| 140 |
結果は上のグラフと CHANGELOG の v3.0.0・v3.1.0 を見てください。
|
| 141 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 142 |
## NPU で初めて動かした 4 つ
|
| 143 |
|
| 144 |
**私たちの知る限り、次の 4 つを NPU で動かして公開している実装は onw が初めてです**(2026 年 9 月、Fable 5.1 調べ)。
|
|
|
|
| 59 |
63.5 対 63.6 MB)。INT4 は 2 行以上のグラフで、NPU 4000 以降のコンパイラーが INT4 を表引き(4 ビットの値をそのまま表の番号に
|
| 60 |
する)に変える処理と重なって失敗し、直してもコンパイルが 20〜35 倍遅くなるだけでした。このため 2 ビットと INT4 のモデル
|
| 61 |
(NPUW を使わないもの)では使わず、融合グラフも既定でオフです(2.1.0 と同じ動作)。INT8 は分離されます
|
| 62 |
+
(llm-jp-4.1-8B INT8 の部品で 2.5 対 216 MB)。Lunar Lake 以降では NF4 版を勧めます。2 ビットは 3.2 で分離できるようにしました(下の「onw 3.2」)。
|
| 63 |
|
| 64 |
### コンパイラーの修正(npu_compiler 8.3 をもとに)
|
| 65 |
|
|
|
|
| 139 |
|
| 140 |
結果は上のグラフと CHANGELOG の v3.0.0・v3.1.0 を見てください。
|
| 141 |
|
| 142 |
+
### onw 3.2:2 ビット(Bonsai)を 64 トークンずつ、重みは 1 組のまま
|
| 143 |
+
|
| 144 |
+
- **重みを持たないグラフを 2 ビットでも**:Bonsai の 2 ビットの重みは 128 個ずつのグループに分けて、グループごとのスケールと
|
| 145 |
+
ゼロ点で量子化されています。コンパイラーは隣り合うグループの切り出しを「仮の土台の上の Concat」にまとめてしまい、重みを
|
| 146 |
+
分離できませんでした。キットのコンパイラーは、同じ OV 定数の連続したグループの切り出しを「先頭+k グループの切り出し+残り」
|
| 147 |
+
に書き直し、[k, N, K] の切り出しはグループごとの Slice → Reshape → Concat で組み立てます(ui2 の Transpose は初期化スケジュール
|
| 148 |
+
の中では使えないため)。`MergeFullyConnected` のまとめはそのまま使えます。
|
| 149 |
+
- **重みの部分一致の共有(プラグイン)**:1 トークン用と 64 トークン用のグラフは、重みの区切り方が違います(片方の 1 本が、もう
|
| 150 |
+
片方の数本をつないだもの)。キットのプラグインは、64 KB 以上の出力を登録しておき、32 KB 以上の新しい出力を既存のバッファの
|
| 151 |
+
中の同じ大きさの倍数の位置と照合して、一致すればその一部を指すようにします(`ONW_WS_SUBRANGE=0` で無効)。2 ビットの重みは
|
| 152 |
+
NPU 上に 1 組だけです。
|
| 153 |
+
- **重みテーブルは共有できない**:NPU(5010 で確認)は、2 ビットのグループのスケールを、出力チャネルごと・グループごとに 16 バイト
|
| 154 |
+
の重みテーブルの行から読みます。テーブルはグラフの中で並びが決まるので、グラフごとに別に要り、重み 1 個あたり 1 ビット
|
| 155 |
+
(2 ビットの重みの半分)になります。新しい形式のテーブル(`useNewWeightTableFormat`)は 5010 の PPE では使えませんでした。
|
| 156 |
+
27B では 1 組 3.4 GB になり、1 トークン用+64 トークン用で NPU 13.8 GB を使ったので、27B には入れていません。
|
| 157 |
+
- **コンパイル時間**:戦略探索(MCSA・兄弟の操作の解析)が、同じ重なり方の分散情報や兄弟のリストを何度も作り直していたのを
|
| 158 |
+
覚えておくようにし、定数の畳み込みのメモを転置・並べ替えにも広げました。定数を左から掛ける行列積がバッチごとに展開される
|
| 159 |
+
所も、右から掛ける形に直します(`ONW_LEFTMM=0` で無効)。2 ビットの 64 トークンの部品 1 つが 335 → 約 20 秒(バイナリーは同じ)。
|
| 160 |
+
- **プロセスでの並列コンパイル**:OpenVINO は 1 プロセスの中の `compile_model` を同時に呼んでも順番に処理し、コンパイラーも
|
| 161 |
+
グラフ 1 つにほぼ 1 コアしか使いません。初回のロードでは、コンパイルして `export_model` で保存するだけの子プロセスを、空き
|
| 162 |
+
メモリ(1 つあたり 2.5 GB)とコア数(半分)に応じて最大 6 つ走らせ、親はできたファイルを読み込みます。子は NPU に重みを
|
| 163 |
+
載せないので、重みは二重になりません(Bonsai 2 4B:819 → 約 180 秒)。子は MoE のエキスパートの束も共有の重みも NPU から
|
| 164 |
+
見えるメモリに読み込みません(最初は読み込んでいて、Qwen3.6-35B-A3B の子 6 つが Panther Lake の NPU ドライバーの中で止まり、
|
| 165 |
+
再起動が要りました。直したあとは 84 部品を 185 秒、初回のロード 231 秒)。
|
| 166 |
+
- **従来のグラフを外す**:融合グラフが覆う長さでは、従来のグラフ(とその重みテーブル)を読み込んだあとで外します。画像の質問や、
|
| 167 |
+
融合グラフより長い文脈になったときだけ読み込み直します(`ONW_FUSED_DROP_PAGED=0` で外さない)。外したグラフの状態のテンソルは
|
| 168 |
+
onw 側で持ちます。
|
| 169 |
+
- **融合グラフの 0 の行**:複数トークンのブロックでは、最後の RMSNorm の二乗和が fp16 であふれて行全体が 0 になることがあります
|
| 170 |
+
(NPU 4000 と 3720 で見ていた現象)。従来のグラフは頭の部分だけ縮めた入力で計算し直していましたが、融合グラフにはそれが
|
| 171 |
+
なく、Bonsai 2 9B の 64 トークンのブロックで Lunar Lake の 256 行中 20 行が一様分布になっていました(KL 0.86)。同じ処理を
|
| 172 |
+
入れて KL 0.02 です。
|
| 173 |
+
- **2 ビットのモデルは先読み検証(PLD)を使いません**:下書きの確認が融合 64 トークンのグラフになり、Lunar Lake で生成が
|
| 174 |
+
23 → 13〜17 tok/s に落ちていました(確認の重さは従来のグラフで測っていたので、オンと判断していました)。
|
| 175 |
+
|
| 176 |
## NPU で初めて動かした 4 つ
|
| 177 |
|
| 178 |
**私たちの知る限り、次の 4 つを NPU で動かして公開している実装は onw が初めてです**(2026 年 9 月、Fable 5.1 調べ)。
|
TECHNICAL_en.md
CHANGED
|
@@ -58,7 +58,7 @@ and compiles were slow. Instead of rewriting the graphs, we fixed the compiler's
|
|
| 58 |
With 2+ rows, INT4 clashes with the NPU 4000+ compiler's rewrite of INT4 into a lookup table (the 4-bit value used as the table
|
| 59 |
index) and fails; fixed, it only compiled 20-35x slower. So 2-bit and INT4 models (those without NPUW) do not use it, and their
|
| 60 |
fused graphs stay off by default (as in 2.1.0). INT8 is separated (an llm-jp-4.1-8B INT8 part: 2.5 vs 216 MB). From Lunar Lake
|
| 61 |
-
on, use the NF4 builds.
|
| 62 |
|
| 63 |
### Compiler fixes (based on npu_compiler 8.3)
|
| 64 |
|
|
@@ -142,6 +142,40 @@ one copy of the weights). With the kit sharing the weights, 3.1 turns NPUW off f
|
|
| 142 |
|
| 143 |
For the results see the chart above and v3.0.0 / v3.1.0 in the CHANGELOG.
|
| 144 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 145 |
## Four firsts on the NPU
|
| 146 |
|
| 147 |
**To our knowledge onw is the first public implementation of these four on the NPU** (survey by Fable 5.1,
|
|
|
|
| 58 |
With 2+ rows, INT4 clashes with the NPU 4000+ compiler's rewrite of INT4 into a lookup table (the 4-bit value used as the table
|
| 59 |
index) and fails; fixed, it only compiled 20-35x slower. So 2-bit and INT4 models (those without NPUW) do not use it, and their
|
| 60 |
fused graphs stay off by default (as in 2.1.0). INT8 is separated (an llm-jp-4.1-8B INT8 part: 2.5 vs 216 MB). From Lunar Lake
|
| 61 |
+
on, use the NF4 builds. 3.2 separates 2-bit weights (see "onw 3.2" below).
|
| 62 |
|
| 63 |
### Compiler fixes (based on npu_compiler 8.3)
|
| 64 |
|
|
|
|
| 142 |
|
| 143 |
For the results see the chart above and v3.0.0 / v3.1.0 in the CHANGELOG.
|
| 144 |
|
| 145 |
+
### onw 3.2: 2-bit (Bonsai) 64 tokens at a time, one copy of the weights
|
| 146 |
+
|
| 147 |
+
- **Graphs without weights for 2-bit too**: Bonsai's 2-bit weights are quantized in groups of 128 with a scale and a zero
|
| 148 |
+
point per group. The compiler folded slices of neighbouring groups into a Concat on a dummy base, which cannot be
|
| 149 |
+
separated. The kit's compiler rewrites consecutive group slices of one OV constant into "prefix + k-group slice + rest",
|
| 150 |
+
and builds a [k, N, K] slice from per-group Slice -> Reshape -> Concat (a ui2 Transpose is not available in the init
|
| 151 |
+
schedule). `MergeFullyConnected` still merges.
|
| 152 |
+
- **Sharing parts of weight buffers (plugin)**: the 1-token and the 64-token graphs cut their weights differently (one
|
| 153 |
+
output of one is several outputs of the other joined). The kit's plugin registers outputs of 64 KB and more and matches a
|
| 154 |
+
new output of 32 KB or more against same-size-aligned offsets inside them; a match points into the existing buffer
|
| 155 |
+
(`ONW_WS_SUBRANGE=0` turns it off). The NPU holds the 2-bit weights once.
|
| 156 |
+
- **Weight tables cannot be shared**: the NPU (seen on 5010) reads a 2-bit group's scale from a 16-byte weight-table row
|
| 157 |
+
per output channel and group. The table layout is fixed per graph, so every graph needs its own: 1 bit per weight, half of
|
| 158 |
+
the 2-bit weights. The newer table format (`useNewWeightTableFormat`) did not work on the 5010's PPE. For the 27B that is
|
| 159 |
+
3.4 GB per set; the 1-token + 64-token sets took 13.8 GB of NPU memory, so the 27B does not get them.
|
| 160 |
+
- **Compile time**: the strategy search (MCSA, sibling-op analysis) rebuilt the same overlapped distributions and sibling
|
| 161 |
+
lists again and again; they are remembered now, and the constant-folding memo covers transposes / permutes too. MatMuls
|
| 162 |
+
with a constant on the left that were unrolled per batch are rewritten with the constant on the right (`ONW_LEFTMM=0`
|
| 163 |
+
turns it off). One 64-token part of a 2-bit model: 335 -> ~20 s (same binary).
|
| 164 |
+
- **Parallel compile in processes**: OpenVINO serializes concurrent `compile_model` calls in one process, and the compiler
|
| 165 |
+
uses about one core per graph. At the first load, up to 6 child processes (2.5 GB of free memory and half a core count
|
| 166 |
+
each) only compile and `export_model`; the parent imports the files. The children never put weights on the NPU, so
|
| 167 |
+
nothing is held twice (Bonsai 2 4B: 819 -> ~180 s). The children load neither a MoE's expert bank nor the shared
|
| 168 |
+
weights into NPU-visible memory (at first they did: six children of Qwen3.6-35B-A3B hung inside Panther Lake's NPU
|
| 169 |
+
driver, a reboot was needed; fixed, 84 parts in 185 s, the first load 231 s).
|
| 170 |
+
- **Dropping the paged graphs**: at lengths the fused graphs cover, the paged graphs (and their weight tables) are released
|
| 171 |
+
after loading; image questions and contexts longer than the fused graphs load them again (`ONW_FUSED_DROP_PAGED=0` keeps
|
| 172 |
+
them). onw keeps the state tensors of the dropped graphs itself.
|
| 173 |
+
- **Zero rows in the fused graphs**: in multi-token blocks the final RMSNorm's sum of squares can overflow fp16 and zero a
|
| 174 |
+
whole row (seen on NPU 4000 and 3720). The paged graphs ran their head again on scaled inputs; the fused ones did not,
|
| 175 |
+
and Bonsai 2 9B's 64-token blocks came out uniform on 20 of 256 rows on Lunar Lake (KL 0.86). With the same fix: 0.02.
|
| 176 |
+
- **2-bit models do not use prompt-lookup decoding**: a draft check would run the fused 64-token graphs (answers 23 ->
|
| 177 |
+
13-17 tok/s on Lunar Lake; the check's cost had been timed on the paged graphs, so it was switched on).
|
| 178 |
+
|
| 179 |
## Four firsts on the NPU
|
| 180 |
|
| 181 |
**To our knowledge onw is the first public implementation of these four on the NPU** (survey by Fable 5.1,
|
onw/__init__.py
CHANGED
|
@@ -2,4 +2,4 @@
|
|
| 2 |
|
| 3 |
A small engine that runs LLMs entirely on the Intel NPU: static segment graphs + host-managed state.
|
| 4 |
"""
|
| 5 |
-
__version__ = "3.
|
|
|
|
| 2 |
|
| 3 |
A small engine that runs LLMs entirely on the Intel NPU: static segment graphs + host-managed state.
|
| 4 |
"""
|
| 5 |
+
__version__ = "3.2.0"
|
onw/app.py
CHANGED
|
@@ -25,9 +25,9 @@ CATALOG = [ # repo, title, takes images, download GB, decode speed (NPU 3720),
|
|
| 25 |
("ryugyosoft/Ternary-Bonsai-2-27B-onw", "Ternary Bonsai 2 27B (2-bit)", False, 7.6, "4 tok/s",
|
| 26 |
"27B を三値(2 ビット)の重みで(Qwen3.8-27B ベースの Ternary Bonsai 2、Lunar Lake 以降専用)。メモリ約 10 GB で 27B が動きますが、プロンプトの処理は 1 トークンずつで遅い(約 4 tok/s)ので、短い質問向け"),
|
| 27 |
("ryugyosoft/Ternary-Bonsai-2-9B-onw", "Ternary Bonsai 2 9B (2-bit)", True, 3.0, "13 tok/s",
|
| 28 |
-
"三値(2 ビット)の 9B(Qwen3.5-9B ベースの Ternary Bonsai 2、Lunar Lake 以降専用)。メモリ約 3.5 GB と軽く、画像も読めます。プロンプト
|
| 29 |
("ryugyosoft/Ternary-Bonsai-2-4B-onw", "Ternary Bonsai 2 4B (2-bit)", True, 1.8, "21 tok/s",
|
| 30 |
-
"三値(2 ビット)の軽い 4B(Qwen3.5-4B ベースの Ternary Bonsai 2、Lunar Lake 以降専用)。メモリ約 2 GB で約 21 tok/s、画像も読めます。プロンプト
|
| 31 |
("ryugyosoft/NeoHorse-1-4B-onw", "NeoHorse-1-4B", False, 2.4, "6.4 tok/s",
|
| 32 |
"エージェント・ツール呼び出し向けの軽量モデル(Qwen3.5-4B ベース)"),
|
| 33 |
("ryugyosoft/granite-4.2-3b-onw", "Granite 4.2 3B", False, 2.1, "7.7 tok/s",
|
|
@@ -75,6 +75,9 @@ DENSE_PROMPT = {"ryugyosoft/Qwen3.5-9B-onw", "ryugyosoft/Ornith-1.5-9B-onw", "ry
|
|
| 75 |
"ryugyosoft/llm-jp-4.1-8b-thinking-onw", "ryugyosoft/llm-jp-4.1-8b-thinking-int8-onw"}
|
| 76 |
# [2.0.3] republished with 2-token graphs: NPU 4000+ answers on them instead of the 1-token ones (chat.block_sizes), whose
|
| 77 |
# compile takes minutes a part there (Granite 4.2 8B first load on Panther Lake 2 h 46 min -> 23 min)
|
|
|
|
|
|
|
|
|
|
| 78 |
DECODE2 = {"ryugyosoft/granite-4.2-3b-onw", "ryugyosoft/granite-4.2-8b-onw", "ryugyosoft/llm-jp-4.1-8b-thinking-onw"}
|
| 79 |
|
| 80 |
|
|
@@ -619,8 +622,8 @@ def outdated(repo, p):
|
|
| 619 |
the INT8 one is noticeably more accurate; the NF4 Qwen3.5 models published before the MTP layer and the 64-token
|
| 620 |
blocks (onw 1.4.0: ~1.3x the answer and 2x the prompt speed on Lunar Lake)."""
|
| 621 |
gpt, nf4, mix = repo.startswith("ryugyosoft/gpt-oss-20b"), repo in NF4_MTP, repo in MOE_MIXER
|
| 622 |
-
dense = repo in DENSE_PROMPT
|
| 623 |
-
if not (gpt or nf4 or mix or dense) or not downloaded_by_onw(p):
|
| 624 |
return False
|
| 625 |
try:
|
| 626 |
meta = json.load(open(os.path.join(p, "engine.json")))
|
|
@@ -628,6 +631,8 @@ def outdated(repo, p):
|
|
| 628 |
return False
|
| 629 |
if mix:
|
| 630 |
return "mixer" not in meta
|
|
|
|
|
|
|
| 631 |
if dense:
|
| 632 |
if repo in DECODE2 and npu_arch() >= 4000 and 2 not in (meta.get("prompt_sizes") or []):
|
| 633 |
return True
|
|
|
|
| 25 |
("ryugyosoft/Ternary-Bonsai-2-27B-onw", "Ternary Bonsai 2 27B (2-bit)", False, 7.6, "4 tok/s",
|
| 26 |
"27B を三値(2 ビット)の重みで(Qwen3.8-27B ベースの Ternary Bonsai 2、Lunar Lake 以降専用)。メモリ約 10 GB で 27B が動きますが、プロンプトの処理は 1 トークンずつで遅い(約 4 tok/s)ので、短い質問向け"),
|
| 27 |
("ryugyosoft/Ternary-Bonsai-2-9B-onw", "Ternary Bonsai 2 9B (2-bit)", True, 3.0, "13 tok/s",
|
| 28 |
+
"三値(2 ビット)の 9B(Qwen3.5-9B ベースの Ternary Bonsai 2、Lunar Lake 以降専用)。メモリ約 3.5 GB と軽く、画像も読めます。onw 3.2 からプロンプトは 64 トークンずつ(Panther Lake で約 190 tok/s)"),
|
| 29 |
("ryugyosoft/Ternary-Bonsai-2-4B-onw", "Ternary Bonsai 2 4B (2-bit)", True, 1.8, "21 tok/s",
|
| 30 |
+
"三値(2 ビット)の軽い 4B(Qwen3.5-4B ベースの Ternary Bonsai 2、Lunar Lake 以降専用)。メモリ約 2 GB で約 21 tok/s、画像も読めます。onw 3.2 からプロンプトは 64 トークンずつ(Panther Lake で約 300 tok/s)"),
|
| 31 |
("ryugyosoft/NeoHorse-1-4B-onw", "NeoHorse-1-4B", False, 2.4, "6.4 tok/s",
|
| 32 |
"エージェント・ツール呼び出し向けの軽量モデル(Qwen3.5-4B ベース)"),
|
| 33 |
("ryugyosoft/granite-4.2-3b-onw", "Granite 4.2 3B", False, 2.1, "7.7 tok/s",
|
|
|
|
| 75 |
"ryugyosoft/llm-jp-4.1-8b-thinking-onw", "ryugyosoft/llm-jp-4.1-8b-thinking-int8-onw"}
|
| 76 |
# [2.0.3] republished with 2-token graphs: NPU 4000+ answers on them instead of the 1-token ones (chat.block_sizes), whose
|
| 77 |
# compile takes minutes a part there (Granite 4.2 8B first load on Panther Lake 2 h 46 min -> 23 min)
|
| 78 |
+
# [3.2] republished with 64-token graphs for onw's kit (engine.json "kit_sizes"): a copy without them is an old download
|
| 79 |
+
# where the kit serves a 2-bit-capable NPU
|
| 80 |
+
U2_PROMPT = {"ryugyosoft/Ternary-Bonsai-2-4B-onw", "ryugyosoft/Ternary-Bonsai-2-9B-onw"}
|
| 81 |
DECODE2 = {"ryugyosoft/granite-4.2-3b-onw", "ryugyosoft/granite-4.2-8b-onw", "ryugyosoft/llm-jp-4.1-8b-thinking-onw"}
|
| 82 |
|
| 83 |
|
|
|
|
| 622 |
the INT8 one is noticeably more accurate; the NF4 Qwen3.5 models published before the MTP layer and the 64-token
|
| 623 |
blocks (onw 1.4.0: ~1.3x the answer and 2x the prompt speed on Lunar Lake)."""
|
| 624 |
gpt, nf4, mix = repo.startswith("ryugyosoft/gpt-oss-20b"), repo in NF4_MTP, repo in MOE_MIXER
|
| 625 |
+
dense, u2 = repo in DENSE_PROMPT, repo in U2_PROMPT
|
| 626 |
+
if not (gpt or nf4 or mix or dense or u2) or not downloaded_by_onw(p):
|
| 627 |
return False
|
| 628 |
try:
|
| 629 |
meta = json.load(open(os.path.join(p, "engine.json")))
|
|
|
|
| 631 |
return False
|
| 632 |
if mix:
|
| 633 |
return "mixer" not in meta
|
| 634 |
+
if u2:
|
| 635 |
+
return "kit_sizes" not in meta and _kit() # (chat.u2_blocks)
|
| 636 |
if dense:
|
| 637 |
if repo in DECODE2 and npu_arch() >= 4000 and 2 not in (meta.get("prompt_sizes") or []):
|
| 638 |
return True
|
onw/chat.py
CHANGED
|
@@ -49,9 +49,9 @@ def kit_shares(core, device, engine_dir, meta):
|
|
| 49 |
(2-bit, INT4: runtime.int4_weights) nor MoE models (experts.bin: their own path)."""
|
| 50 |
from .runtime import npu_arch, int4_weights
|
| 51 |
return (device == "NPU" and npukit.active() and npu_arch(core) >= 4000
|
| 52 |
-
and (meta.get("needs") != "u2" or os.environ.get("ONW_U2_WEIGHTLESS") =
|
| 53 |
and not os.path.exists(os.path.join(engine_dir, "experts.bin"))
|
| 54 |
-
and (not int4_weights(engine_dir, meta) or os.environ.get("
|
| 55 |
and os.environ.get("ONW_WEIGHTLESS", "1") != "0")
|
| 56 |
|
| 57 |
|
|
@@ -63,6 +63,13 @@ def use_mtp(meta, shared):
|
|
| 63 |
return bool(meta.get("mtp")) and (env == "1" if env in ("0", "1") else shared)
|
| 64 |
|
| 65 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
def block_sizes(meta, shared=False, mtp=False, arch=None):
|
| 67 |
"""Block sizes to load: the 1-token graphs for answers and ONE prompt size, the largest the model has ([2.0]: in
|
| 68 |
the 16-token graphs' place - with the weights shared between the sizes (NPUW) every size still adds its NPU
|
|
@@ -71,6 +78,10 @@ def block_sizes(meta, shared=False, mtp=False, arch=None):
|
|
| 71 |
1-token graphs. ONW_S64=0: the 16-token graphs as before; 1: every size. arch: the NPU's (runtime.npu_arch) -
|
| 72 |
NPU 3720 takes blocks up to 64 tokens (128 are slower there: its INT4 matmuls stop scaling); ONW_PROMPT_MAX: a cap."""
|
| 73 |
sizes = all_sizes(meta)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 74 |
only = os.environ.get("ONW_SIZES") # e.g. "16": decode with the 16-token graphs only
|
| 75 |
if not only and 1 in sizes and grouped_wanted(meta):
|
| 76 |
# [2.0] a MoE model with mixer graphs: every prompt goes the grouped-MoE path (onw/grouped.py), so only the
|
|
@@ -115,6 +126,34 @@ def block_sizes(meta, shared=False, mtp=False, arch=None):
|
|
| 115 |
THINK_BUDGET = {"low": 512, "medium": 2048, "high": None}
|
| 116 |
|
| 117 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 118 |
class ChatEngine:
|
| 119 |
_think_marks = None # the thought markers when they are not <think> ... </think> (Gemma 4)
|
| 120 |
swap_gb = None # memory-saving mode: GB of the 1-token graphs swapped in and out
|
|
@@ -248,8 +287,26 @@ class ChatEngine:
|
|
| 248 |
# [2.1.0] the answer graphs as stream_chat sets them for an answer of the usual length (n + up to 1024 new
|
| 249 |
# tokens: the 2K variant), so the first question does not switch variants (Panther Lake, Granite 4.2 8B
|
| 250 |
# NF4: 6 s added to the first prompt)
|
| 251 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 252 |
m.fused_prepare(min(1024 if S > min(m._fused_sizes) else 2048, m.T), S)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 253 |
pld_state = "off (memory-saving mode)" if self.swap_gb is not None else "on" if self.pld else "off"
|
| 254 |
print(f"[load] {time.time()-t0:.1f}s on {device}; verify block = {self.block_cost or 0:.1f} single steps, "
|
| 255 |
f"prompt lookup decoding {pld_state}", flush=True)
|
|
@@ -258,6 +315,10 @@ class ChatEngine:
|
|
| 258 |
def venc(self):
|
| 259 |
if self._venc is None:
|
| 260 |
t0 = time.time()
|
|
|
|
|
|
|
|
|
|
|
|
|
| 261 |
self._venc = self._compile_vision(*self._venc_args)
|
| 262 |
print(f"[vision] encoder loaded in {time.time() - t0:.1f}s", flush=True)
|
| 263 |
return self._venc
|
|
@@ -854,6 +915,12 @@ class ChatEngine:
|
|
| 854 |
self.model.fused_prepare(self.model.cur + ahead)
|
| 855 |
# exact only for plain greedy; not while block sizes are swapped (the verification blocks go away mid-answer)
|
| 856 |
use_pld = self.pld and plain_greedy and self.model.S > 1 and not swap and budget is None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 857 |
# MTP drafts (Qwen3.5 dense): every sampling setting (exact), the thinking budget included
|
| 858 |
mtp = getattr(self.model, "mtp", None) is not None and not swap and any(S >= 4 for S in [*self.model.segs, *(getattr(self.model, "_fused", None) or {})])
|
| 859 |
use_pld = use_pld and not mtp
|
|
|
|
| 49 |
(2-bit, INT4: runtime.int4_weights) nor MoE models (experts.bin: their own path)."""
|
| 50 |
from .runtime import npu_arch, int4_weights
|
| 51 |
return (device == "NPU" and npukit.active() and npu_arch(core) >= 4000
|
| 52 |
+
and (meta.get("needs") != "u2" or os.environ.get("ONW_U2_WEIGHTLESS", "1") != "0") # [3.2] on
|
| 53 |
and not os.path.exists(os.path.join(engine_dir, "experts.bin"))
|
| 54 |
+
and (not int4_weights(engine_dir, meta) or os.environ.get("ONW_INT4_WEIGHTLESS") == "1")
|
| 55 |
and os.environ.get("ONW_WEIGHTLESS", "1") != "0")
|
| 56 |
|
| 57 |
|
|
|
|
| 63 |
return bool(meta.get("mtp")) and (env == "1" if env in ("0", "1") else shared)
|
| 64 |
|
| 65 |
|
| 66 |
+
def u2_blocks():
|
| 67 |
+
"""[3.2] 2-bit (Bonsai) blocks of 2+ tokens: with onw's kit (Windows and Linux: Bonsai 2 4B / 9B checked on Panther
|
| 68 |
+
Lake and on Lunar Lake under Ubuntu). ONW_U2_BLOCKS=1 / 0 forces them on / off."""
|
| 69 |
+
env = os.environ.get("ONW_U2_BLOCKS")
|
| 70 |
+
return env == "1" if env in ("0", "1") else npukit.active()
|
| 71 |
+
|
| 72 |
+
|
| 73 |
def block_sizes(meta, shared=False, mtp=False, arch=None):
|
| 74 |
"""Block sizes to load: the 1-token graphs for answers and ONE prompt size, the largest the model has ([2.0]: in
|
| 75 |
the 16-token graphs' place - with the weights shared between the sizes (NPUW) every size still adds its NPU
|
|
|
|
| 78 |
1-token graphs. ONW_S64=0: the 16-token graphs as before; 1: every size. arch: the NPU's (runtime.npu_arch) -
|
| 79 |
NPU 3720 takes blocks up to 64 tokens (128 are slower there: its INT4 matmuls stop scaling); ONW_PROMPT_MAX: a cap."""
|
| 80 |
sizes = all_sizes(meta)
|
| 81 |
+
if meta.get("needs") == "u2" and not u2_blocks():
|
| 82 |
+
# [3.2] 2-bit weights in blocks of 2+ rows need onw's kit (its compiler has the fix of npu_compiler #355: the
|
| 83 |
+
# stock compilers return zeros for them) - the 1-token graphs only without it
|
| 84 |
+
sizes = [S for S in sizes if S == 1] or sizes
|
| 85 |
only = os.environ.get("ONW_SIZES") # e.g. "16": decode with the 16-token graphs only
|
| 86 |
if not only and 1 in sizes and grouped_wanted(meta):
|
| 87 |
# [2.0] a MoE model with mixer graphs: every prompt goes the grouped-MoE path (onw/grouped.py), so only the
|
|
|
|
| 126 |
THINK_BUDGET = {"low": 512, "medium": 2048, "high": None}
|
| 127 |
|
| 128 |
|
| 129 |
+
def vision_blob(d, c):
|
| 130 |
+
"""[3.2] onw's blob store path of the vision encoder for config c (the plain route), or None."""
|
| 131 |
+
from .runtime import blob_path, NO_KEY
|
| 132 |
+
if not c.get("CACHE_DIR") or any(k.startswith("NPU_USE_NPUW") for k in c) or os.environ.get("ONW_OWN_BLOBS") == "0":
|
| 133 |
+
return None
|
| 134 |
+
xml = os.path.join(d, "vision.xml")
|
| 135 |
+
nocache = {k: v for k, v in c.items() if k != "CACHE_DIR"}
|
| 136 |
+
return blob_path(c["CACHE_DIR"], "vision.xml", [open(xml, "rb").read(), os.path.getsize(xml[:-4] + ".bin"),
|
| 137 |
+
ov.__version__, json.dumps({k: v for k, v in nocache.items()
|
| 138 |
+
if k not in NO_KEY}, sort_keys=True)])
|
| 139 |
+
|
| 140 |
+
|
| 141 |
+
def export_vision_blob(core, d, device, c):
|
| 142 |
+
"""[3.2] Compile the vision encoder and write its blob (a background child at a first load: the first image
|
| 143 |
+
then only imports it - Panther Lake, Bonsai 2 4B: 144 s at the first image before)."""
|
| 144 |
+
import io
|
| 145 |
+
bp = vision_blob(d, c)
|
| 146 |
+
if bp is None or os.path.exists(bp) or os.path.exists(bp + ".bad"):
|
| 147 |
+
return
|
| 148 |
+
first = core.compile_model(os.path.join(d, "vision.xml"), device, c)
|
| 149 |
+
buf = io.BytesIO()
|
| 150 |
+
first.export_model(buf)
|
| 151 |
+
os.makedirs(os.path.dirname(bp), exist_ok=True)
|
| 152 |
+
with open(f"{bp}.{os.getpid()}.tmp", "wb") as f:
|
| 153 |
+
f.write(buf.getbuffer())
|
| 154 |
+
os.replace(f"{bp}.{os.getpid()}.tmp", bp)
|
| 155 |
+
|
| 156 |
+
|
| 157 |
class ChatEngine:
|
| 158 |
_think_marks = None # the thought markers when they are not <think> ... </think> (Gemma 4)
|
| 159 |
swap_gb = None # memory-saving mode: GB of the 1-token graphs swapped in and out
|
|
|
|
| 287 |
# [2.1.0] the answer graphs as stream_chat sets them for an answer of the usual length (n + up to 1024 new
|
| 288 |
# tokens: the 2K variant), so the first question does not switch variants (Panther Lake, Granite 4.2 8B
|
| 289 |
# NF4: 6 s added to the first prompt)
|
| 290 |
+
# [3.2] memory first: the paged graphs of those sizes go before the fused ones load, and the 1-token ones
|
| 291 |
+
# load first - their merged 2-bit group weights are the buffers the 64-token graphs then share (a graph
|
| 292 |
+
# must not share a buffer of a graph that is dropped later: Bonsai 2 9B crashed in the NPU driver)
|
| 293 |
+
for S in sorted(m._fused_sizes):
|
| 294 |
+
if hasattr(m, "_drop_paged_for_fused"):
|
| 295 |
+
m._drop_paged_for_fused(S, before=True)
|
| 296 |
+
for S in sorted(m._fused_sizes):
|
| 297 |
m.fused_prepare(min(1024 if S > min(m._fused_sizes) else 2048, m.T), S)
|
| 298 |
+
self._venc_job = None
|
| 299 |
+
if self.vision and device == "NPU" and os.environ.get("ONW_VISION_PRECOMPILE", "1") != "0":
|
| 300 |
+
vb = vision_blob(engine_dir, self._venc_args[3])
|
| 301 |
+
if vb is not None and not os.path.exists(vb) and not os.path.exists(vb + ".bad"):
|
| 302 |
+
import subprocess, sys
|
| 303 |
+
flags = 0x00004000 if os.name == "nt" else 0 # (BELOW_NORMAL_PRIORITY_CLASS)
|
| 304 |
+
self._venc_job = subprocess.Popen(
|
| 305 |
+
[sys.executable, "-m", "onw.visionjob", json.dumps([engine_dir, device, self._venc_args[3]],
|
| 306 |
+
default=str)],
|
| 307 |
+
env={**os.environ, "PYTHONUTF8": "1"}, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
|
| 308 |
+
creationflags=flags)
|
| 309 |
+
print("[vision] encoder compiling in the background", flush=True)
|
| 310 |
pld_state = "off (memory-saving mode)" if self.swap_gb is not None else "on" if self.pld else "off"
|
| 311 |
print(f"[load] {time.time()-t0:.1f}s on {device}; verify block = {self.block_cost or 0:.1f} single steps, "
|
| 312 |
f"prompt lookup decoding {pld_state}", flush=True)
|
|
|
|
| 315 |
def venc(self):
|
| 316 |
if self._venc is None:
|
| 317 |
t0 = time.time()
|
| 318 |
+
job = getattr(self, "_venc_job", None)
|
| 319 |
+
if job is not None: # [3.2] (being compiled in the background: wait for its blob)
|
| 320 |
+
job.wait()
|
| 321 |
+
self._venc_job = None
|
| 322 |
self._venc = self._compile_vision(*self._venc_args)
|
| 323 |
print(f"[vision] encoder loaded in {time.time() - t0:.1f}s", flush=True)
|
| 324 |
return self._venc
|
|
|
|
| 915 |
self.model.fused_prepare(self.model.cur + ahead)
|
| 916 |
# exact only for plain greedy; not while block sizes are swapped (the verification blocks go away mid-answer)
|
| 917 |
use_pld = self.pld and plain_greedy and self.model.S > 1 and not swap and budget is None
|
| 918 |
+
fz = sorted(getattr(self.model, "_fused", None) or [])
|
| 919 |
+
if use_pld and fz[:1] == [1] and not any(1 < S <= 16 for S in fz) and os.environ.get("ONW_PLD_BIG") != "1":
|
| 920 |
+
# [3.2] answers on the fused 1-token graphs with no small block beside them (2-bit models: [1, 64]): a draft
|
| 921 |
+
# check would take the fused 64-token graphs, many fused steps (block_cost was timed on the paged graphs,
|
| 922 |
+
# whose single steps are slower - Bonsai 2 4B on Lunar Lake: answers 23 -> 13-17 tok/s with PLD)
|
| 923 |
+
use_pld = False
|
| 924 |
# MTP drafts (Qwen3.5 dense): every sampling setting (exact), the thinking budget included
|
| 925 |
mtp = getattr(self.model, "mtp", None) is not None and not swap and any(S >= 4 for S in [*self.model.segs, *(getattr(self.model, "_fused", None) or {})])
|
| 926 |
use_pld = use_pld and not mtp
|
onw/fused.py
CHANGED
|
@@ -209,7 +209,7 @@ def wanted(m):
|
|
| 209 |
# fused graphs ran out of memory)
|
| 210 |
from .runtime import _WEIGHTLESS
|
| 211 |
on = (npukit.active() and npu_arch(m._ctx["core"]) >= 4000
|
| 212 |
-
and (m.meta.get("needs") != "u2" or os.environ.get("ONW_U2_WEIGHTLESS") =
|
| 213 |
and not _WEIGHTLESS.get("skip_model") and _WEIGHTLESS["ok"] is not False and not _WEIGHTLESS.get("full"))
|
| 214 |
if os.environ.get("ONW_FUSED", "1" if on else "0") != "1":
|
| 215 |
return None
|
|
@@ -264,8 +264,9 @@ class FusedDecode:
|
|
| 264 |
self.mask_kv_t = mk(ov.Type.f16, [1, self.Ct])
|
| 265 |
self.mask_self_t = mk(ov.Type.f16, [S, S])
|
| 266 |
t0, built = time.time(), 0
|
|
|
|
| 267 |
for name, sm in sorted(m.meta["segments"].items()):
|
| 268 |
-
if sm["S"] != S:
|
| 269 |
continue
|
| 270 |
# (NPUW: the model's shared mapping of the weight files and NPUW's reading, so its weights bank sees the
|
| 271 |
# same constants as for the other block size - one copy of the weights)
|
|
@@ -441,7 +442,12 @@ class FusedDecode:
|
|
| 441 |
"prev": {k: v.copy() for k, v in m.state.items() if k not in m._pp} if n > 1 else None,
|
| 442 |
"pp": {k: e[1] for k, e in m._pp.items()} if n > 1 else None}
|
| 443 |
logits, pend = None, None
|
| 444 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 445 |
req = st["req"]
|
| 446 |
if st["head"] and m.mtp is not None and st["h_in"] is not None:
|
| 447 |
m.last_h = np.asarray(st["h_in"].data[0, :n], np.float32) # (what the MTP layer drafts from)
|
|
@@ -451,11 +457,15 @@ class FusedDecode:
|
|
| 451 |
bufs, c = m._pp[k]
|
| 452 |
req.set_tensor(k, bufs[c])
|
| 453 |
req.set_tensor("ssm_new." + k[4:], bufs[1 - c])
|
|
|
|
|
|
|
| 454 |
req.start_async()
|
| 455 |
if pend is not None: # the previous segment's new rows while this one runs
|
| 456 |
pend()
|
| 457 |
pend = None
|
| 458 |
req.wait()
|
|
|
|
|
|
|
| 459 |
|
| 460 |
if n > 1:
|
| 461 |
for o in st["rows"]: # (the request's own buffer: read by truncate() before the
|
|
@@ -479,9 +489,31 @@ class FusedDecode:
|
|
| 479 |
pend = write
|
| 480 |
if st["head"]:
|
| 481 |
logits = np.array(req.get_tensor("logits").data[0, :n])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 482 |
if pend is not None:
|
| 483 |
pend()
|
| 484 |
m.cur += n
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 485 |
if logits is not None:
|
| 486 |
if not np.isfinite(logits).all():
|
| 487 |
raise FloatingPointError(f"the fused {S}-token graphs returned NaN/inf logits")
|
|
|
|
| 209 |
# fused graphs ran out of memory)
|
| 210 |
from .runtime import _WEIGHTLESS
|
| 211 |
on = (npukit.active() and npu_arch(m._ctx["core"]) >= 4000
|
| 212 |
+
and (m.meta.get("needs") != "u2" or os.environ.get("ONW_U2_WEIGHTLESS", "1") != "0") # [3.2] on
|
| 213 |
and not _WEIGHTLESS.get("skip_model") and _WEIGHTLESS["ok"] is not False and not _WEIGHTLESS.get("full"))
|
| 214 |
if os.environ.get("ONW_FUSED", "1" if on else "0") != "1":
|
| 215 |
return None
|
|
|
|
| 264 |
self.mask_kv_t = mk(ov.Type.f16, [1, self.Ct])
|
| 265 |
self.mask_self_t = mk(ov.Type.f16, [S, S])
|
| 266 |
t0, built = time.time(), 0
|
| 267 |
+
only = os.environ.get("ONW_FUSED_SEG") if compile_only else None # [3.2] (a child: this segment only)
|
| 268 |
for name, sm in sorted(m.meta["segments"].items()):
|
| 269 |
+
if sm["S"] != S or (only and name != only):
|
| 270 |
continue
|
| 271 |
# (NPUW: the model's shared mapping of the weight files and NPUW's reading, so its weights bank sees the
|
| 272 |
# same constants as for the other block size - one copy of the weights)
|
|
|
|
| 442 |
"prev": {k: v.copy() for k, v in m.state.items() if k not in m._pp} if n > 1 else None,
|
| 443 |
"pp": {k: e[1] for k, e in m._pp.items()} if n > 1 else None}
|
| 444 |
logits, pend = None, None
|
| 445 |
+
import os, time
|
| 446 |
+
prof = None
|
| 447 |
+
if os.environ.get("ONW_PROFILE") == "1": # [3.2] per-segment NPU times, every 64 steps
|
| 448 |
+
prof = self.__dict__.setdefault("_prof", {"n": 0, "seg": [0.0] * len(self.plan), "step": 0.0})
|
| 449 |
+
t_step = time.perf_counter()
|
| 450 |
+
for si, st in enumerate(self.plan):
|
| 451 |
req = st["req"]
|
| 452 |
if st["head"] and m.mtp is not None and st["h_in"] is not None:
|
| 453 |
m.last_h = np.asarray(st["h_in"].data[0, :n], np.float32) # (what the MTP layer drafts from)
|
|
|
|
| 457 |
bufs, c = m._pp[k]
|
| 458 |
req.set_tensor(k, bufs[c])
|
| 459 |
req.set_tensor("ssm_new." + k[4:], bufs[1 - c])
|
| 460 |
+
if prof is not None:
|
| 461 |
+
t0 = time.perf_counter()
|
| 462 |
req.start_async()
|
| 463 |
if pend is not None: # the previous segment's new rows while this one runs
|
| 464 |
pend()
|
| 465 |
pend = None
|
| 466 |
req.wait()
|
| 467 |
+
if prof is not None:
|
| 468 |
+
prof["seg"][si] += time.perf_counter() - t0
|
| 469 |
|
| 470 |
if n > 1:
|
| 471 |
for o in st["rows"]: # (the request's own buffer: read by truncate() before the
|
|
|
|
| 489 |
pend = write
|
| 490 |
if st["head"]:
|
| 491 |
logits = np.array(req.get_tensor("logits").data[0, :n])
|
| 492 |
+
dead = ~logits.any(axis=1)
|
| 493 |
+
if dead.any() and st["h_in"] is not None:
|
| 494 |
+
# [3.2] rows of all zeros: the final RMSNorm's sum of squares overflowed fp16 (large activations in
|
| 495 |
+
# multi-token blocks - Bonsai 2 9B 64-token blocks on Lunar Lake, 20 of 256 rows), as
|
| 496 |
+
# SegmentedModel._head_again: the norm does not care about scale, so the head runs again on the
|
| 497 |
+
# hidden states divided by their max |h| (written in place: the next step overwrites them anyway)
|
| 498 |
+
h = st["h_in"].data
|
| 499 |
+
x = np.asarray(h[0, :n], np.float32)
|
| 500 |
+
h[0, :n] = (x / np.maximum(np.abs(x).max(-1, keepdims=True), 1e-2)).astype(h.dtype)
|
| 501 |
+
req.infer()
|
| 502 |
+
logits[dead] = np.array(req.get_tensor("logits").data[0, :n])[dead]
|
| 503 |
+
if not getattr(m, "_dead_note", False):
|
| 504 |
+
print("[head] logit rows of all zeros (fp16 overflow in the final norm) computed again, scaled",
|
| 505 |
+
flush=True)
|
| 506 |
+
m._dead_note = True
|
| 507 |
if pend is not None:
|
| 508 |
pend()
|
| 509 |
m.cur += n
|
| 510 |
+
if prof is not None:
|
| 511 |
+
prof["step"] += time.perf_counter() - t_step
|
| 512 |
+
prof["n"] += 1
|
| 513 |
+
if prof["n"] % 64 == 0:
|
| 514 |
+
k = prof["n"]
|
| 515 |
+
print(f"[profile] fused {S}-token, {k} steps: {prof['step'] / k * 1e3:.2f} ms a step, segments "
|
| 516 |
+
+ " ".join(f"{v / k * 1e3:.2f}" for v in prof["seg"]), flush=True)
|
| 517 |
if logits is not None:
|
| 518 |
if not np.isfinite(logits).all():
|
| 519 |
raise FloatingPointError(f"the fused {S}-token graphs returned NaN/inf logits")
|
onw/i18n.json
CHANGED
|
@@ -355,8 +355,8 @@
|
|
| 355 |
"27B を三値(2 ビット)の重みで(Qwen3.8-27B ベースの Ternary Bonsai 2、Lunar Lake 以降専用)。メモリ約 10 GB で 27B が動きますが、プロンプトの処理は 1 トークンずつで遅い(約 4 tok/s)ので、短い質問向け": "27B with ternary (2-bit) weights (Ternary Bonsai 2, from Qwen3.8-27B; Lunar Lake and newer only). A 27B in about 10 GB of memory - but prompts are processed token by token, slowly (~4 tok/s): for short questions",
|
| 356 |
"NPU を性能優先で動かす(ターボ)": "Run the NPU for speed (turbo)",
|
| 357 |
"NPU のクロックを上げて、回答とプロンプトの読み込みを速くします(Lunar Lake で 2〜4 割、Panther Lake で 2 割ほど)。そのぶん消費電力が増えます(Lunar Lake で約 6 W)。電池で長く使いたいときはオフに。": "Raises the NPU's clocks: answers and prompt reading get faster (20-40% on Lunar Lake, ~20% on Panther Lake), at the cost of more power (~6 W on Lunar Lake). Turn it off for long battery use.",
|
| 358 |
-
"三値(2 ビット)の 9B(Qwen3.5-9B ベースの Ternary Bonsai 2、Lunar Lake 以降専用)。メモリ約 3.5 GB と軽く、画像も読めます。プロンプト
|
| 359 |
-
"三値(2 ビット)の軽い 4B(Qwen3.5-4B ベースの Ternary Bonsai 2、Lunar Lake 以降専用)。メモリ約 2 GB で約 21 tok/s、画像も読めます。プロンプト
|
| 360 |
"Qwen 系を高速化する": "Speed up the Qwen family",
|
| 361 |
"Qwen3.5・Ornith などで、区間ごとの融合グラフと MTP 先読みを使います。プロンプトの処理が 2〜4 割、回答が 1 割ほど速くなり、メモリを約 1 GB 多く使います。Lunar Lake 以降で、メモリ節約モードがオフのときに働きます。": "Qwen3.5, Ornith and similar models run fused segment graphs with MTP draft tokens: prompts 20-40% and answers about 10% faster, about 1 GB more memory. Works from Lunar Lake on, while the memory-saving mode is off.",
|
| 362 |
"モデルを検索": "Search models",
|
|
@@ -726,8 +726,8 @@
|
|
| 726 |
"27B を三値(2 ビット)の重みで(Qwen3.8-27B ベースの Ternary Bonsai 2、Lunar Lake 以降専用)。メモリ約 10 GB で 27B が動きますが、プロンプトの処理は 1 トークンずつで遅い(約 4 tok/s)ので、短い質問向け": "三值(2 位)权重的 27B(基于 Qwen3.8-27B 的 Ternary Bonsai 2,仅限 Lunar Lake 及更新的 NPU)。约 10 GB 内存即可运行 27B,但提示词逐个 token 处理、较慢(约 4 tok/s),适合简短提问",
|
| 727 |
"NPU を性能優先で動かす(ターボ)": "NPU 性能优先运行(加速模式)",
|
| 728 |
"NPU のクロックを上げて、回答とプロンプトの読み込みを速くします(Lunar Lake で 2〜4 割、Panther Lake で 2 割ほど)。そのぶん消費電力が増えます(Lunar Lake で約 6 W)。電池で長く使いたいときはオフに。": "提高 NPU 频率,回答和提示词读取更快(Lunar Lake 约快 20~40%,Panther Lake 约 20%),但功耗增加(Lunar Lake 约 6 W)。需要长时间使用电池时请关闭。",
|
| 729 |
-
"三値(2 ビット)の 9B(Qwen3.5-9B ベースの Ternary Bonsai 2、Lunar Lake 以降専用)。メモリ約 3.5 GB と軽く、画像も読めます。プロンプト
|
| 730 |
-
"三値(2 ビット)の軽い 4B(Qwen3.5-4B ベースの Ternary Bonsai 2、Lunar Lake 以降専用)。メモリ約 2 GB で約 21 tok/s、画像も読めます。プロンプト
|
| 731 |
"Qwen 系を高速化する": "加速 Qwen 系列",
|
| 732 |
"Qwen3.5・Ornith などで、区間ごとの融合グラフと MTP 先読みを使います。プロンプトの処理が 2〜4 割、回答が 1 割ほど速くなり、メモリを約 1 GB 多く使います。Lunar Lake 以降で、メモリ節約モードがオフのときに働きます。": "Qwen3.5、Ornith 等模型使用按区段融合的图和 MTP 预测:提示处理快 2〜4 成,回答快约 1 成,内存多用约 1 GB。适用于 Lunar Lake 及之后的机型,且内存节省模式关闭时。",
|
| 733 |
"モデルを検索": "搜索模型",
|
|
|
|
| 355 |
"27B を三値(2 ビット)の重みで(Qwen3.8-27B ベースの Ternary Bonsai 2、Lunar Lake 以降専用)。メモリ約 10 GB で 27B が動きますが、プロンプトの処理は 1 トークンずつで遅い(約 4 tok/s)ので、短い質問向け": "27B with ternary (2-bit) weights (Ternary Bonsai 2, from Qwen3.8-27B; Lunar Lake and newer only). A 27B in about 10 GB of memory - but prompts are processed token by token, slowly (~4 tok/s): for short questions",
|
| 356 |
"NPU を性能優先で動かす(ターボ)": "Run the NPU for speed (turbo)",
|
| 357 |
"NPU のクロックを上げて、回答とプロンプトの読み込みを速くします(Lunar Lake で 2〜4 割、Panther Lake で 2 割ほど)。そのぶん消費電力が増えます(Lunar Lake で約 6 W)。電池で長く使いたいときはオフに。": "Raises the NPU's clocks: answers and prompt reading get faster (20-40% on Lunar Lake, ~20% on Panther Lake), at the cost of more power (~6 W on Lunar Lake). Turn it off for long battery use.",
|
| 358 |
+
"三値(2 ビット)の 9B(Qwen3.5-9B ベースの Ternary Bonsai 2、Lunar Lake 以降専用)。メモリ約 3.5 GB と軽く、画像も読めます。onw 3.2 からプロンプトは 64 トークンずつ(Panther Lake で約 190 tok/s)": "Ternary (2-bit) 9B (Ternary Bonsai 2, based on Qwen3.5-9B; Lunar Lake and later only). Light at about 3.5 GB of memory and reads images. From onw 3.2 prompts go 64 tokens at a time (~190 tok/s on Panther Lake)",
|
| 359 |
+
"三値(2 ビット)の軽い 4B(Qwen3.5-4B ベースの Ternary Bonsai 2、Lunar Lake 以降専用)。メモリ約 2 GB で約 21 tok/s、画像も読めます。onw 3.2 からプロンプトは 64 トークンずつ(Panther Lake で約 300 tok/s)": "A light ternary (2-bit) 4B (Ternary Bonsai 2, based on Qwen3.5-4B; Lunar Lake and later only). About 2 GB of memory, about 21 tok/s, reads images. From onw 3.2 prompts go 64 tokens at a time (~300 tok/s on Panther Lake)",
|
| 360 |
"Qwen 系を高速化する": "Speed up the Qwen family",
|
| 361 |
"Qwen3.5・Ornith などで、区間ごとの融合グラフと MTP 先読みを使います。プロンプトの処理が 2〜4 割、回答が 1 割ほど速くなり、メモリを約 1 GB 多く使います。Lunar Lake 以降で、メモリ節約モードがオフのときに働きます。": "Qwen3.5, Ornith and similar models run fused segment graphs with MTP draft tokens: prompts 20-40% and answers about 10% faster, about 1 GB more memory. Works from Lunar Lake on, while the memory-saving mode is off.",
|
| 362 |
"モデルを検索": "Search models",
|
|
|
|
| 726 |
"27B を三値(2 ビット)の重みで(Qwen3.8-27B ベースの Ternary Bonsai 2、Lunar Lake 以降専用)。メモリ約 10 GB で 27B が動きますが、プロンプトの処理は 1 トークンずつで遅い(約 4 tok/s)ので、短い質問向け": "三值(2 位)权重的 27B(基于 Qwen3.8-27B 的 Ternary Bonsai 2,仅限 Lunar Lake 及更新的 NPU)。约 10 GB 内存即可运行 27B,但提示词逐个 token 处理、较慢(约 4 tok/s),适合简短提问",
|
| 727 |
"NPU を性能優先で動かす(ターボ)": "NPU 性能优先运行(加速模式)",
|
| 728 |
"NPU のクロックを上げて、回答とプロンプトの読み込みを速くします(Lunar Lake で 2〜4 割、Panther Lake で 2 割ほど)。そのぶん消費電力が増えます(Lunar Lake で約 6 W)。電池で長く使いたいときはオフに。": "提高 NPU 频率,回答和提示词读取更快(Lunar Lake 约快 20~40%,Panther Lake 约 20%),但功耗增加(Lunar Lake 约 6 W)。需要长时间使用电池时请关闭。",
|
| 729 |
+
"三値(2 ビット)の 9B(Qwen3.5-9B ベースの Ternary Bonsai 2、Lunar Lake 以降専用)。メモリ約 3.5 GB と軽く、画像も読めます。onw 3.2 からプロンプトは 64 トークンずつ(Panther Lake で約 190 tok/s)": "三值(2 位)9B(基于 Qwen3.5-9B 的 Ternary Bonsai 2,仅限 Lunar Lake 及以后)。内存约 3.5 GB,很轻,也能读图。自 onw 3.2 起提示词每次处理 64 个词元(Panther Lake 上约 190 tok/s)",
|
| 730 |
+
"三値(2 ビット)の軽い 4B(Qwen3.5-4B ベースの Ternary Bonsai 2、Lunar Lake 以降専用)。メモリ約 2 GB で約 21 tok/s、画像も読めます。onw 3.2 からプロンプトは 64 トークンずつ(Panther Lake で約 300 tok/s)": "轻量的三值(2 位)4B(基于 Qwen3.5-4B 的 Ternary Bonsai 2,仅限 Lunar Lake 及以后)。内存约 2 GB,约 21 tok/s,也能读图。自 onw 3.2 起提示词每次处理 64 个词元(Panther Lake 上约 300 tok/s)",
|
| 731 |
"Qwen 系を高速化する": "加速 Qwen 系列",
|
| 732 |
"Qwen3.5・Ornith などで、区間ごとの融合グラフと MTP 先読みを使います。プロンプトの処理が 2〜4 割、回答が 1 割ほど速くなり、メモリを約 1 GB 多く使います。Lunar Lake 以降で、メモリ節約モードがオフのときに働きます。": "Qwen3.5、Ornith 等模型使用按区段融合的图和 MTP 预测:提示处理快 2〜4 成,回答快约 1 成,内存多用约 1 GB。适用于 Lunar Lake 及之后的机型,且内存节省模式关闭时。",
|
| 733 |
"モデルを検索": "搜索模型",
|
onw/ir.py
CHANGED
|
@@ -9,6 +9,8 @@ Conventions shared by every model builder:
|
|
| 9 |
the output side. Those are the weights-as-inputs forms the NPU runs at full speed (group-wise scales on
|
| 10 |
runtime weights turn into slow element-wise work), and they let the host bind just the selected experts.
|
| 11 |
"""
|
|
|
|
|
|
|
| 12 |
import numpy as np
|
| 13 |
import openvino as ov
|
| 14 |
import openvino.opset13 as ops
|
|
@@ -197,6 +199,12 @@ def ternary_weight(w):
|
|
| 197 |
G = i // w.group
|
| 198 |
t = ov.Tensor(ov.Type.u2, [o, G, w.group])
|
| 199 |
np.copyto(t.data, np.asarray(w.packed).view(np.uint8).reshape(t.data.shape))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 200 |
d = ops.multiply(ops.subtract(ops.convert(ops.constant(t), ov.Type.f16), ops.constant(np.array(1.0, np.float16))),
|
| 201 |
ops.constant(np.asarray(w.scales, np.float16).reshape(o, G, 1)))
|
| 202 |
return ops.convert(ops.reshape(d, i64([o, i]), False), F32)
|
|
|
|
| 9 |
the output side. Those are the weights-as-inputs forms the NPU runs at full speed (group-wise scales on
|
| 10 |
runtime weights turn into slow element-wise work), and they let the host bind just the selected experts.
|
| 11 |
"""
|
| 12 |
+
import os
|
| 13 |
+
|
| 14 |
import numpy as np
|
| 15 |
import openvino as ov
|
| 16 |
import openvino.opset13 as ops
|
|
|
|
| 199 |
G = i // w.group
|
| 200 |
t = ov.Tensor(ov.Type.u2, [o, G, w.group])
|
| 201 |
np.copyto(t.data, np.asarray(w.packed).view(np.uint8).reshape(t.data.shape))
|
| 202 |
+
if os.environ.get("ONW_TERNARY_F32") == "1":
|
| 203 |
+
# (test) the chain in the f32 form NPUW's asymmetric-weight pattern (npuw/partitioning/patterns/dcoff.cpp,
|
| 204 |
+
# AsymmZP::CWAI) recognizes: only the 2-bit codes go to its shared weights bank, zero point and scale stay
|
| 205 |
+
d = ops.multiply(ops.subtract(ops.convert(ops.constant(t), F32), ops.constant(np.array(1.0, np.float32))),
|
| 206 |
+
ops.constant(np.asarray(w.scales, np.float32).reshape(o, G, 1)))
|
| 207 |
+
return ops.reshape(d, i64([o, i]), False)
|
| 208 |
d = ops.multiply(ops.subtract(ops.convert(ops.constant(t), ov.Type.f16), ops.constant(np.array(1.0, np.float16))),
|
| 209 |
ops.constant(np.asarray(w.scales, np.float16).reshape(o, G, 1)))
|
| 210 |
return ops.convert(ops.reshape(d, i64([o, i]), False), F32)
|
onw/npukit/NOTICE.txt
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
onw NPU kit (onw 3.
|
| 2 |
|
| 3 |
This folder holds binaries built by the onw project from open-source code of Intel Corporation, licensed under the
|
| 4 |
Apache License 2.0 (LICENSE). They are modified versions; the modifications are in patches/.
|
|
|
|
| 1 |
+
onw NPU kit (onw 3.2)
|
| 2 |
|
| 3 |
This folder holds binaries built by the onw project from open-source code of Intel Corporation, licensed under the
|
| 4 |
Apache License 2.0 (LICENSE). They are modified versions; the modifications are in patches/.
|
onw/npukit/kit.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
{
|
| 2 |
-
"version": "3.
|
| 3 |
"openvino": "2026.5.0-23160-6db412d0a27",
|
| 4 |
"files": [
|
| 5 |
"openvino_intel_npu_plugin.dll",
|
|
|
|
| 1 |
{
|
| 2 |
+
"version": "3.2.0 (npu_compiler 8.3 + onw fixes, NPU plugin 2026.5.0 6db412d + weight sharing)",
|
| 3 |
"openvino": "2026.5.0-23160-6db412d0a27",
|
| 4 |
"files": [
|
| 5 |
"openvino_intel_npu_plugin.dll",
|
onw/npukit/linux/npukit-linux.tar.xz
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
-
size
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:2eeec38edc79a59b39e52f4f65b3bdb1b6cba5f944f189aa2a2343057ef5efa8
|
| 3 |
+
size 33102668
|
onw/npukit/openvino_intel_npu_compiler.dll
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
-
size
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:609f80dad80b510eab914352b02dedf61c8744e0b5fc6da59c6816ba7ae2f105
|
| 3 |
+
size 84021760
|
onw/npukit/openvino_intel_npu_plugin.dll
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
-
size
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:fe7fd8fa7f0a5a69b7ad627a9d5568d5a0206cdedc0fd01d92fd5e590732a39e
|
| 3 |
+
size 8001024
|
onw/npukit/patches/npu_compiler-20e7912.patch
CHANGED
|
@@ -51,6 +51,44 @@ index 3943fd7..c15b6aa 100644
|
|
| 51 |
|
| 52 |
VPU::DistributionInfoAttr updateSliceLikeOpsAlignment(mlir::MLIRContext* ctx, vpux::ShapeRef inShape,
|
| 53 |
vpux::ShapeRef sliceShape,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
diff --git a/src/vpux_compiler/include/vpux/compiler/dialect/VPU/utils/multi_cluster_strategy_utils.hpp b/src/vpux_compiler/include/vpux/compiler/dialect/VPU/utils/multi_cluster_strategy_utils.hpp
|
| 55 |
index 8895bf9..1e0b4d3 100644
|
| 56 |
--- a/src/vpux_compiler/include/vpux/compiler/dialect/VPU/utils/multi_cluster_strategy_utils.hpp
|
|
@@ -74,6 +112,67 @@ index 8895bf9..1e0b4d3 100644
|
|
| 74 |
};
|
| 75 |
vpux::VPU::StrategyCost correctSwOpCost(VPU::SWOpInterface swOp, ArrayRef<vpux::NDTypeInterface> tiledInputTypes,
|
| 76 |
vpux::VPU::StrategyCost cost);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
diff --git a/src/vpux_compiler/include/vpux/compiler/dialect/VPU/utils/vertical_fusion/v2/merge_vf_region_rewriter.hpp b/src/vpux_compiler/include/vpux/compiler/dialect/VPU/utils/vertical_fusion/v2/merge_vf_region_rewriter.hpp
|
| 78 |
index fe48940..c819038 100644
|
| 79 |
--- a/src/vpux_compiler/include/vpux/compiler/dialect/VPU/utils/vertical_fusion/v2/merge_vf_region_rewriter.hpp
|
|
@@ -87,8 +186,32 @@ index fe48940..c819038 100644
|
|
| 87 |
StrategyCost getOriginalVFCost(VPU::VerticalFusionOp vfOp) const;
|
| 88 |
|
| 89 |
VFCacheAnalysis& _cache;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 90 |
diff --git a/src/vpux_compiler/src/compiler.cpp b/src/vpux_compiler/src/compiler.cpp
|
| 91 |
-
index 263a325..
|
| 92 |
--- a/src/vpux_compiler/src/compiler.cpp
|
| 93 |
+++ b/src/vpux_compiler/src/compiler.cpp
|
| 94 |
@@ -1087,7 +1087,11 @@ void compileIEtoVPU(mlir::OwningOpRef<mlir::ModuleOp>& moduleOp,
|
|
@@ -117,6 +240,22 @@ index 263a325..acf446c 100644
|
|
| 117 |
vpuipPM.addPass(VPU::createDestructWsAnalysisPass(log));
|
| 118 |
}
|
| 119 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 120 |
diff --git a/src/vpux_compiler/src/conversion/passes/IE2VPU/convert_layers_to_VPU.cpp b/src/vpux_compiler/src/conversion/passes/IE2VPU/convert_layers_to_VPU.cpp
|
| 121 |
index f80f56e..3cfef97 100644
|
| 122 |
--- a/src/vpux_compiler/src/conversion/passes/IE2VPU/convert_layers_to_VPU.cpp
|
|
@@ -203,6 +342,76 @@ index f80f56e..3cfef97 100644
|
|
| 203 |
|
| 204 |
mlir::ConversionTarget target(ctx);
|
| 205 |
target.addIllegalDialect<IE::IEDialect>();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 206 |
diff --git a/src/vpux_compiler/src/dialect/IE/transforms/passes/swap_convert_with_reshape_kind_ops.cpp b/src/vpux_compiler/src/dialect/IE/transforms/passes/swap_convert_with_reshape_kind_ops.cpp
|
| 207 |
index d16690c..93f292f 100644
|
| 208 |
--- a/src/vpux_compiler/src/dialect/IE/transforms/passes/swap_convert_with_reshape_kind_ops.cpp
|
|
@@ -304,7 +513,7 @@ index 7390492..4dd29e4 100644
|
|
| 304 |
case VPU::MultiClusterStrategy::Clustering:
|
| 305 |
isStrategyCompatible = true;
|
| 306 |
diff --git a/src/vpux_compiler/src/dialect/VPU/transforms/passes/multi_cluster_strategy_assignment.cpp b/src/vpux_compiler/src/dialect/VPU/transforms/passes/multi_cluster_strategy_assignment.cpp
|
| 307 |
-
index adaca74..
|
| 308 |
--- a/src/vpux_compiler/src/dialect/VPU/transforms/passes/multi_cluster_strategy_assignment.cpp
|
| 309 |
+++ b/src/vpux_compiler/src/dialect/VPU/transforms/passes/multi_cluster_strategy_assignment.cpp
|
| 310 |
@@ -5,6 +5,10 @@
|
|
@@ -318,7 +527,14 @@ index adaca74..fab49de 100644
|
|
| 318 |
#include "vpux/compiler/dialect/VPU/utils/cost_model/layer_vpunn_cost.hpp"
|
| 319 |
#include "vpux/compiler/dialect/VPU/utils/manual_strategy_utils.hpp"
|
| 320 |
#include "vpux/compiler/dialect/VPU/utils/multi_cluster_strategy_utils.hpp"
|
| 321 |
-
@@ -
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 322 |
_log.debug("Greedy Strategy Assignment");
|
| 323 |
auto enableMultiClusterForSWLayer =
|
| 324 |
config::getAvailableExecutor(module, config::ExecutorKind::SHAVE_ACT) != nullptr;
|
|
@@ -340,6 +556,331 @@ index adaca74..fab49de 100644
|
|
| 340 |
|
| 341 |
_log.trace("Remove Temporary Strategy");
|
| 342 |
strategyManager.removeTemporaryMulticlusterStrategy();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 343 |
diff --git a/src/vpux_compiler/src/dialect/VPU/utils/strategy_manager/multi_cluster_strategy_utils.cpp b/src/vpux_compiler/src/dialect/VPU/utils/strategy_manager/multi_cluster_strategy_utils.cpp
|
| 344 |
index 4bfec49..26e3194 100644
|
| 345 |
--- a/src/vpux_compiler/src/dialect/VPU/utils/strategy_manager/multi_cluster_strategy_utils.cpp
|
|
@@ -772,7 +1313,7 @@ index 4207b33..7c9f3d1 100644
|
|
| 772 |
return mlir::success();
|
| 773 |
}
|
| 774 |
diff --git a/src/vpux_compiler/src/dialect/VPU/utils/vertical_fusion/v2/merge_vf_region_rewriter.cpp b/src/vpux_compiler/src/dialect/VPU/utils/vertical_fusion/v2/merge_vf_region_rewriter.cpp
|
| 775 |
-
index f3d8660..
|
| 776 |
--- a/src/vpux_compiler/src/dialect/VPU/utils/vertical_fusion/v2/merge_vf_region_rewriter.cpp
|
| 777 |
+++ b/src/vpux_compiler/src/dialect/VPU/utils/vertical_fusion/v2/merge_vf_region_rewriter.cpp
|
| 778 |
@@ -28,6 +28,43 @@
|
|
@@ -925,7 +1466,7 @@ index f3d8660..2e84289 100644
|
|
| 925 |
return std::nullopt;
|
| 926 |
}
|
| 927 |
|
| 928 |
-
@@ -543,
|
| 929 |
return true;
|
| 930 |
}
|
| 931 |
|
|
@@ -953,12 +1494,12 @@ index f3d8660..2e84289 100644
|
|
| 953 |
+llvm::hash_code onwHashVF(VPU::VerticalFusionOp op) {
|
| 954 |
+ llvm::hash_code h = llvm::hash_value(op->getAttrDictionary().getAsOpaquePointer());
|
| 955 |
+ for (auto v : op->getOperands()) {
|
| 956 |
-
+ h = llvm::hash_combine(h, onwHashType(v.getType()));
|
| 957 |
+ }
|
| 958 |
+ op.getBody()->walk([&](mlir::Operation* o) {
|
| 959 |
+ h = llvm::hash_combine(h, o->getName().getAsOpaquePointer(), o->getAttrDictionary().getAsOpaquePointer());
|
| 960 |
-
+ for (auto v : o->getOperands()) {
|
| 961 |
-
+ h = llvm::hash_combine(h, onwHashType(v.getType()));
|
| 962 |
+ }
|
| 963 |
+ for (auto t : o->getResultTypes()) {
|
| 964 |
+ h = llvm::hash_combine(h, onwHashType(t));
|
|
@@ -987,7 +1528,26 @@ index f3d8660..2e84289 100644
|
|
| 987 |
_log.trace("Starting vertical fusion for region with VerticalFusionOp {0} at location {1}", vfOp, vfOp->getLoc());
|
| 988 |
if (vfOp.getIsManualConfigured()) {
|
| 989 |
_log.trace("Skipping vertical fusion for manually configured VerticalFusionOp at location {0}", vfOp->getLoc());
|
| 990 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 991 |
|
| 992 |
VPU::VerticalFusionOp vfBlock = nullptr;
|
| 993 |
VPU::VerticalFusionOp parentVFOp = nullptr;
|
|
@@ -1015,7 +1575,7 @@ index f3d8660..2e84289 100644
|
|
| 1015 |
|
| 1016 |
_log.trace("Analyzing vertical fusion region with parent VerticalFusionOp {0} at location {1}", parentVFOp,
|
| 1017 |
parentVFOp->getLoc());
|
| 1018 |
-
@@ -580,7 +
|
| 1019 |
continue;
|
| 1020 |
}
|
| 1021 |
|
|
@@ -1027,7 +1587,7 @@ index f3d8660..2e84289 100644
|
|
| 1027 |
_log.trace("Merged subgraph candidate {0}", vfBlock.getLoc());
|
| 1028 |
// `findVFCase` internally compares operations using IR order which is calculated "on the fly" in e.g.
|
| 1029 |
// `isBeforeInBlock()`
|
| 1030 |
-
@@ -590,15 +
|
| 1031 |
// As `findVFCase` is expected to not modify the IR, what can be done is an eager recalculation
|
| 1032 |
// of the operation orders that would ensure later calls to e.g. `isBeforeInBlock()` become concurrent
|
| 1033 |
// reads instead of concurrent writes (which is no longer a data race).
|
|
@@ -1066,7 +1626,7 @@ index f3d8660..2e84289 100644
|
|
| 1066 |
if (checkOtherVFInput(vfOp, parentVFOp)) {
|
| 1067 |
continue;
|
| 1068 |
}
|
| 1069 |
-
@@ -614,7 +
|
| 1070 |
}
|
| 1071 |
|
| 1072 |
_log.trace("Merged subgraph {0}", vfBlock->getLoc());
|
|
@@ -1078,8 +1638,47 @@ index f3d8660..2e84289 100644
|
|
| 1078 |
|
| 1079 |
return mlir::success();
|
| 1080 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1081 |
diff --git a/src/vpux_compiler/src/dialect/VPU/utils/weights_separation.cpp b/src/vpux_compiler/src/dialect/VPU/utils/weights_separation.cpp
|
| 1082 |
-
index 35a6c00..
|
| 1083 |
--- a/src/vpux_compiler/src/dialect/VPU/utils/weights_separation.cpp
|
| 1084 |
+++ b/src/vpux_compiler/src/dialect/VPU/utils/weights_separation.cpp
|
| 1085 |
@@ -4,6 +4,7 @@
|
|
@@ -1090,7 +1689,50 @@ index 35a6c00..d48e08c 100644
|
|
| 1090 |
#include "vpux/compiler/core/force_link_macros.hpp"
|
| 1091 |
#include "vpux/compiler/dialect/IE/IR/ops/data_movement.hpp"
|
| 1092 |
#include "vpux/compiler/dialect/IE/IR/ops/data_type.hpp"
|
| 1093 |
-
@@ -
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1094 |
/// Returns an IE operation for the given constant transformation.
|
| 1095 |
mlir::Value createMatchingIeOperation(mlir::OpBuilder& builder, mlir::Location loc, mlir::Value input,
|
| 1096 |
Const::TransformAttrInterface t) {
|
|
@@ -1100,7 +1742,7 @@ index 35a6c00..d48e08c 100644
|
|
| 1100 |
return llvm::TypeSwitch<Const::TransformAttrInterface, mlir::Value>(t)
|
| 1101 |
.Case<Const::AddAttr>([&](Const::AddAttr add) {
|
| 1102 |
const auto biasValue = checked_cast<float>(add.getBias().getValueAsDouble());
|
| 1103 |
-
@@ -398,15 +
|
| 1104 |
const auto inElemType = mlir::cast<NDTypeInterface>(input.getType()).getElementType();
|
| 1105 |
const auto outElemType = cast.getElemType();
|
| 1106 |
|
|
@@ -1197,6 +1839,518 @@ index 35a6c00..d48e08c 100644
|
|
| 1197 |
const auto inElemType = mlir::dyn_cast_or_null<mlir::quant::QuantizedType>(
|
| 1198 |
mlir::cast<NDTypeInterface>(input.getType()).getElementType());
|
| 1199 |
const auto outElemType = mlir::dyn_cast_or_null<mlir::quant::QuantizedType>(convert.getElemType());
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1200 |
diff --git a/src/vpux_compiler/src/dialect/VPUIP/transforms/passes/convert_async_ops_to_tasks.cpp b/src/vpux_compiler/src/dialect/VPUIP/transforms/passes/convert_async_ops_to_tasks.cpp
|
| 1201 |
index f637bad..6c3db25 100644
|
| 1202 |
--- a/src/vpux_compiler/src/dialect/VPUIP/transforms/passes/convert_async_ops_to_tasks.cpp
|
|
@@ -1293,7 +2447,7 @@ index 23fe4fc..b93a938 100644
|
|
| 1293 |
}
|
| 1294 |
|
| 1295 |
diff --git a/src/vpux_compiler/src/dialect/const/attributes/content.cpp b/src/vpux_compiler/src/dialect/const/attributes/content.cpp
|
| 1296 |
-
index 3f4f862..
|
| 1297 |
--- a/src/vpux_compiler/src/dialect/const/attributes/content.cpp
|
| 1298 |
+++ b/src/vpux_compiler/src/dialect/const/attributes/content.cpp
|
| 1299 |
@@ -7,6 +7,14 @@
|
|
@@ -1311,7 +2465,7 @@ index 3f4f862..f5d66fe 100644
|
|
| 1311 |
#include "vpux/compiler/dialect/const/dialect.hpp"
|
| 1312 |
#include "vpux/compiler/dialect/const/utils/const_logger.hpp"
|
| 1313 |
#include "vpux/compiler/utils/stable_hash.hpp"
|
| 1314 |
-
@@ -468,11 +476,
|
| 1315 |
// ContentAttr::fold
|
| 1316 |
//
|
| 1317 |
|
|
@@ -1348,18 +2502,24 @@ index 3f4f862..f5d66fe 100644
|
|
| 1348 |
- for (const auto& attr : getTransformations()) {
|
| 1349 |
- res = attr.transform(res);
|
| 1350 |
+ const auto transformations = getTransformations();
|
| 1351 |
-
+ // the last SubView that follows a Concat: the prefix before it is memoized
|
|
|
|
|
|
|
|
|
|
| 1352 |
+ int64_t cut = -1;
|
| 1353 |
+ bool sawConcat = false;
|
|
|
|
| 1354 |
+ for (size_t i = 0; i < transformations.size(); ++i) {
|
| 1355 |
-
+ if (mlir::isa<Const::ConcatAttr>(transformations[i])
|
|
|
|
| 1356 |
+ sawConcat = true;
|
| 1357 |
+ } else if (sawConcat && mlir::isa<Const::SubViewAttr>(transformations[i])) {
|
| 1358 |
+ cut = static_cast<int64_t>(i);
|
| 1359 |
+ }
|
| 1360 |
+ }
|
| 1361 |
+ static std::atomic<int64_t> nHit{0}, nMiss{0}, nPlain{0};
|
| 1362 |
-
+
|
|
|
|
| 1363 |
+ ++nPlain;
|
| 1364 |
+ auto res = wrapBaseContent(baseContent);
|
| 1365 |
+ for (const auto& attr : transformations) {
|
|
@@ -1403,7 +2563,10 @@ index 3f4f862..f5d66fe 100644
|
|
| 1403 |
+ for (size_t i = static_cast<size_t>(cut) + 1; i < transformations.size(); ++i) {
|
| 1404 |
+ res = transformations[i].transform(res);
|
| 1405 |
+ }
|
| 1406 |
-
+
|
|
|
|
|
|
|
|
|
|
| 1407 |
+ }
|
| 1408 |
+ if (!hit) {
|
| 1409 |
+ ++nMiss;
|
|
@@ -1432,6 +2595,105 @@ index 3f4f862..f5d66fe 100644
|
|
| 1432 |
}
|
| 1433 |
return res;
|
| 1434 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1435 |
diff --git a/src/vpux_compiler/src/utils/dma_transaction_utils.cpp b/src/vpux_compiler/src/utils/dma_transaction_utils.cpp
|
| 1436 |
index 10cb1f4..45e02e8 100644
|
| 1437 |
--- a/src/vpux_compiler/src/utils/dma_transaction_utils.cpp
|
|
@@ -1453,3 +2715,9 @@ index 10cb1f4..45e02e8 100644
|
|
| 1453 |
VPUX_THROW_WHEN(std::any_of(std::begin(reducedBitStrides), std::prev(std::end(reducedBitStrides)),
|
| 1454 |
[](auto bitStride) {
|
| 1455 |
return bitStride.count() % CHAR_BIT != 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
|
| 52 |
VPU::DistributionInfoAttr updateSliceLikeOpsAlignment(mlir::MLIRContext* ctx, vpux::ShapeRef inShape,
|
| 53 |
vpux::ShapeRef sliceShape,
|
| 54 |
+
diff --git a/src/vpux_compiler/include/vpux/compiler/dialect/VPU/utils/mpe_engine_utils.hpp b/src/vpux_compiler/include/vpux/compiler/dialect/VPU/utils/mpe_engine_utils.hpp
|
| 55 |
+
index b058405..e8d6cb5 100644
|
| 56 |
+
--- a/src/vpux_compiler/include/vpux/compiler/dialect/VPU/utils/mpe_engine_utils.hpp
|
| 57 |
+
+++ b/src/vpux_compiler/include/vpux/compiler/dialect/VPU/utils/mpe_engine_utils.hpp
|
| 58 |
+
@@ -5,7 +5,10 @@
|
| 59 |
+
|
| 60 |
+
#pragma once
|
| 61 |
+
|
| 62 |
+
+#include <cstdlib>
|
| 63 |
+
+
|
| 64 |
+
#include "vpux/compiler/core/types/quantile_float/types.hpp"
|
| 65 |
+
+#include "vpux/compiler/dialect/IE/IR/ops/convolution.hpp"
|
| 66 |
+
#include "vpux/compiler/dialect/IE/IR/ops/eltwise.hpp"
|
| 67 |
+
#include "vpux/compiler/dialect/IE/IR/ops/pooling.hpp"
|
| 68 |
+
#include "vpux/compiler/dialect/IE/utils/matmul.hpp"
|
| 69 |
+
@@ -23,8 +26,20 @@ namespace vpux::VPU {
|
| 70 |
+
*/
|
| 71 |
+
class MPEEngineConfig {
|
| 72 |
+
public:
|
| 73 |
+
- static bool useNewWeightTableFormat(mlir::Operation*, bool) {
|
| 74 |
+
- return false;
|
| 75 |
+
+ // onw: ONW_NEW_WT=1 - the split weight table (data pointers / scale / bias as separate operands) on NPU 50XX: the
|
| 76 |
+
+ // per-channel scales then come from the model's constants (weightless: computed by the init schedule) instead of
|
| 77 |
+
+ // being baked into the table rows, so graphs that differ only in weights compile to the same blob (test).
|
| 78 |
+
+ static bool useNewWeightTableFormat(mlir::Operation* op, bool isCompressConv) {
|
| 79 |
+
+ static const bool on = std::getenv("ONW_NEW_WT") != nullptr;
|
| 80 |
+
+ if (!on || op == nullptr || isCompressConv) {
|
| 81 |
+
+ return false;
|
| 82 |
+
+ }
|
| 83 |
+
+ // only plain convolutions: depthwise / pooling / eltwise NCE ops do not take the split table in this build
|
| 84 |
+
+ // (NCE.DepthConvolution got null scale / bias operands)
|
| 85 |
+
+ if (!mlir::isa<IE::ConvolutionOp, VPU::NCEConvolutionOp>(op)) {
|
| 86 |
+
+ return false;
|
| 87 |
+
+ }
|
| 88 |
+
+ return config::getArch(op) == config::ArchKind::NPU50XX;
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
static bool isNewWeightTableFormatSupportedWithDwOps([[maybe_unused]] config::ArchKind arch) {
|
| 92 |
diff --git a/src/vpux_compiler/include/vpux/compiler/dialect/VPU/utils/multi_cluster_strategy_utils.hpp b/src/vpux_compiler/include/vpux/compiler/dialect/VPU/utils/multi_cluster_strategy_utils.hpp
|
| 93 |
index 8895bf9..1e0b4d3 100644
|
| 94 |
--- a/src/vpux_compiler/include/vpux/compiler/dialect/VPU/utils/multi_cluster_strategy_utils.hpp
|
|
|
|
| 112 |
};
|
| 113 |
vpux::VPU::StrategyCost correctSwOpCost(VPU::SWOpInterface swOp, ArrayRef<vpux::NDTypeInterface> tiledInputTypes,
|
| 114 |
vpux::VPU::StrategyCost cost);
|
| 115 |
+
diff --git a/src/vpux_compiler/include/vpux/compiler/dialect/VPU/utils/sibling_ops_analysis.hpp b/src/vpux_compiler/include/vpux/compiler/dialect/VPU/utils/sibling_ops_analysis.hpp
|
| 116 |
+
index 2eaaece..513fb6e 100644
|
| 117 |
+
--- a/src/vpux_compiler/include/vpux/compiler/dialect/VPU/utils/sibling_ops_analysis.hpp
|
| 118 |
+
+++ b/src/vpux_compiler/include/vpux/compiler/dialect/VPU/utils/sibling_ops_analysis.hpp
|
| 119 |
+
@@ -10,6 +10,7 @@
|
| 120 |
+
#include <mlir/Dialect/Func/IR/FuncOps.h>
|
| 121 |
+
#include <mlir/IR/BuiltinTypes.h>
|
| 122 |
+
|
| 123 |
+
+#include <deque>
|
| 124 |
+
#include <set>
|
| 125 |
+
#include <vector>
|
| 126 |
+
|
| 127 |
+
@@ -33,13 +34,48 @@ public:
|
| 128 |
+
// ClusteredOp(producer) -> View-like op -> ClusteredOp(consumer)
|
| 129 |
+
virtual std::set<ClusteredOpInterface> getConsumers(ClusteredOpInterface);
|
| 130 |
+
|
| 131 |
+
+ // onw: the same groups as getSiblings / getConsumers (same order), as views of the stored group - those return a
|
| 132 |
+
+ // copy of the std::set, which was 21% of the strategy search on 2-bit (Bonsai) graphs. The views stay valid while
|
| 133 |
+
+ // the analysis lives. Eager analyses (no lazy fill) answer through the copying calls.
|
| 134 |
+
+ ArrayRef<ClusteredOpInterface> onwSiblingsView(ClusteredOpInterface);
|
| 135 |
+
+ ArrayRef<ClusteredOpInterface> onwConsumersView(ClusteredOpInterface);
|
| 136 |
+
+
|
| 137 |
+
protected:
|
| 138 |
+
+ bool _onwLazy = true;
|
| 139 |
+
+ std::deque<SmallVector<ClusteredOpInterface>> _onwGroupVecs{};
|
| 140 |
+
+ llvm::DenseMap<mlir::Operation*, size_t> _onwConsumerGroup{};
|
| 141 |
+
+ SmallVector<ClusteredOpInterface> _onwScratch{};
|
| 142 |
+
std::set<ClusteredOpInterface> lookupOpSiblings(mlir::Operation* op);
|
| 143 |
+
mlir::Operation* getConsumerOp(ClusteredOpInterface);
|
| 144 |
+
+ // onw: adds a group and indexes it by its members and by the op it was computed for
|
| 145 |
+
+ void addGroup(mlir::Operation* queried, std::set<ClusteredOpInterface> group);
|
| 146 |
+
std::vector<std::set<ClusteredOpInterface>> _siblingGroups{};
|
| 147 |
+
+ // onw: op -> index of the first group that holds it (or was computed for it); lookups were a linear scan of every
|
| 148 |
+
+ // group, and an op found in none recomputed its siblings (and appended another group) on every call - the strategy
|
| 149 |
+
+ // search of 2-bit (Bonsai) graphs, whose weights split into 20 per-group convolutions on one input, spent 56% there
|
| 150 |
+
+ llvm::DenseMap<mlir::Operation*, size_t> _onwGroupOf{};
|
| 151 |
+
llvm::DenseMap<ClusteredOpInterface, std::set<ClusteredOpInterface>> _consumers{};
|
| 152 |
+
};
|
| 153 |
+
|
| 154 |
+
+// onw: the analysis of the pass running on this thread (MultiClusterStrategyAssignment publishes its own) - the
|
| 155 |
+
+// overload of getOutputOverlappedParams without an analysis (the split-over-height checks) built a fresh one on every
|
| 156 |
+
+// call, so the sibling search ran again for every op and strategy (2-bit Bonsai parts: 89% of a 6-minute compile).
|
| 157 |
+
+// ONW_NO_SIBLING_CACHE=1: as before.
|
| 158 |
+
+extern thread_local SiblingOpsAnalysis* onwCurrentSiblingAnalysis;
|
| 159 |
+
+// onw: drops this thread's overlapped-params cache (overlap_distribution_utils.cpp)
|
| 160 |
+
+void onwClearOverlapCache();
|
| 161 |
+
+struct OnwSiblingAnalysisScope {
|
| 162 |
+
+ SiblingOpsAnalysis* prev;
|
| 163 |
+
+ explicit OnwSiblingAnalysisScope(SiblingOpsAnalysis* a): prev(onwCurrentSiblingAnalysis) {
|
| 164 |
+
+ onwCurrentSiblingAnalysis = a;
|
| 165 |
+
+ onwClearOverlapCache();
|
| 166 |
+
+ }
|
| 167 |
+
+ ~OnwSiblingAnalysisScope() {
|
| 168 |
+
+ onwCurrentSiblingAnalysis = prev;
|
| 169 |
+
+ onwClearOverlapCache();
|
| 170 |
+
+ }
|
| 171 |
+
+};
|
| 172 |
+
+
|
| 173 |
+
// Eager version of SiblingOpsAnalysis which doesn't modify internal state
|
| 174 |
+
// on calls to getConsumers/getSiblings. Suitable for usage with multithreading.
|
| 175 |
+
class EagerSiblingOpsAnalysis : public SiblingOpsAnalysis {
|
| 176 |
diff --git a/src/vpux_compiler/include/vpux/compiler/dialect/VPU/utils/vertical_fusion/v2/merge_vf_region_rewriter.hpp b/src/vpux_compiler/include/vpux/compiler/dialect/VPU/utils/vertical_fusion/v2/merge_vf_region_rewriter.hpp
|
| 177 |
index fe48940..c819038 100644
|
| 178 |
--- a/src/vpux_compiler/include/vpux/compiler/dialect/VPU/utils/vertical_fusion/v2/merge_vf_region_rewriter.hpp
|
|
|
|
| 186 |
StrategyCost getOriginalVFCost(VPU::VerticalFusionOp vfOp) const;
|
| 187 |
|
| 188 |
VFCacheAnalysis& _cache;
|
| 189 |
+
diff --git a/src/vpux_compiler/include/vpux/compiler/dialect/VPU/utils/weights_separation.hpp b/src/vpux_compiler/include/vpux/compiler/dialect/VPU/utils/weights_separation.hpp
|
| 190 |
+
index 0beeab6..3d54599 100644
|
| 191 |
+
--- a/src/vpux_compiler/include/vpux/compiler/dialect/VPU/utils/weights_separation.hpp
|
| 192 |
+
+++ b/src/vpux_compiler/include/vpux/compiler/dialect/VPU/utils/weights_separation.hpp
|
| 193 |
+
@@ -10,6 +10,9 @@
|
| 194 |
+
#include "vpux/compiler/utils/logging.hpp"
|
| 195 |
+
|
| 196 |
+
#include "mlir/Dialect/Func/IR/FuncOps.h"
|
| 197 |
+
+
|
| 198 |
+
+#include <map>
|
| 199 |
+
+#include <vector>
|
| 200 |
+
#include "mlir/Pass/AnalysisManager.h"
|
| 201 |
+
|
| 202 |
+
namespace vpux::VPU {
|
| 203 |
+
@@ -234,6 +237,9 @@ class ConstOpConverter {
|
| 204 |
+
// Note: operation cache is pimpl-ed to not expose it as a public class.
|
| 205 |
+
class OperationCache;
|
| 206 |
+
std::unique_ptr<OperationCache> _operationCache;
|
| 207 |
+
+ // onw: results of folded sub-byte view chains (foldSubByteView) by input value and chain - identical chains (the
|
| 208 |
+
+ // tiles of one weight) must give one value, i.e. one Init result, as the per-transformation cache does
|
| 209 |
+
+ std::map<std::vector<const void*>, mlir::Value> _onwViewCache;
|
| 210 |
+
|
| 211 |
+
// IR building function with internal caching semantics.
|
| 212 |
+
std::tuple<ArrayRef<Const::TransformAttrInterface>, mlir::Value> createMatchingOperation(
|
| 213 |
diff --git a/src/vpux_compiler/src/compiler.cpp b/src/vpux_compiler/src/compiler.cpp
|
| 214 |
+
index 263a325..419221a 100644
|
| 215 |
--- a/src/vpux_compiler/src/compiler.cpp
|
| 216 |
+++ b/src/vpux_compiler/src/compiler.cpp
|
| 217 |
@@ -1087,7 +1087,11 @@ void compileIEtoVPU(mlir::OwningOpRef<mlir::ModuleOp>& moduleOp,
|
|
|
|
| 240 |
vpuipPM.addPass(VPU::createDestructWsAnalysisPass(log));
|
| 241 |
}
|
| 242 |
|
| 243 |
+
@@ -1154,6 +1160,7 @@ std::vector<CompilationResult> compileImplWsOneShot(
|
| 244 |
+
mlir::OwningOpRef<mlir::ModuleOp> moduleMain =
|
| 245 |
+
importNetwork(ctx, model, originalParameters, originalResults, config, devConf, rootTiming, log,
|
| 246 |
+
/*enableWeightsSeparationPath=*/true);
|
| 247 |
+
+ moduleMain.get()->setAttr("VPU.OnwWeightless", mlir::UnitAttr::get(ctx)); // onw: see MergeParallelFullyConnected
|
| 248 |
+
|
| 249 |
+
OV_ITT_TASK_NEXT(COMPILER_IMPLEMENTATION, "compile");
|
| 250 |
+
auto factoryMethod = createDialectPipelineStrategyFn(config);
|
| 251 |
+
@@ -1276,6 +1283,7 @@ std::tuple<mlir::OwningOpRef<mlir::ModuleOp>, bool> compileImplWsIterative(
|
| 252 |
+
OV_ITT_TASK_NEXT(COMPILER_IMPLEMENTATION, "importNetwork");
|
| 253 |
+
auto moduleOp = importNetwork(ctx, model, originalParameters, originalResults, config, devConf, rootTiming, log,
|
| 254 |
+
/*enableWeightsSeparationPath=*/true);
|
| 255 |
+
+ moduleOp.get()->setAttr("VPU.OnwWeightless", mlir::UnitAttr::get(ctx)); // onw: see MergeParallelFullyConnected
|
| 256 |
+
|
| 257 |
+
auto factoryMethod = createDialectPipelineStrategyFn(config);
|
| 258 |
+
auto mainPipelineStrategy = factoryMethod(config::CompilationMode::WSMain);
|
| 259 |
diff --git a/src/vpux_compiler/src/conversion/passes/IE2VPU/convert_layers_to_VPU.cpp b/src/vpux_compiler/src/conversion/passes/IE2VPU/convert_layers_to_VPU.cpp
|
| 260 |
index f80f56e..3cfef97 100644
|
| 261 |
--- a/src/vpux_compiler/src/conversion/passes/IE2VPU/convert_layers_to_VPU.cpp
|
|
|
|
| 342 |
|
| 343 |
mlir::ConversionTarget target(ctx);
|
| 344 |
target.addIllegalDialect<IE::IEDialect>();
|
| 345 |
+
diff --git a/src/vpux_compiler/src/dialect/IE/transforms/passes/forbid_four_bit_outputs.cpp b/src/vpux_compiler/src/dialect/IE/transforms/passes/forbid_four_bit_outputs.cpp
|
| 346 |
+
index bc08bb0..d67d826 100644
|
| 347 |
+
--- a/src/vpux_compiler/src/dialect/IE/transforms/passes/forbid_four_bit_outputs.cpp
|
| 348 |
+
+++ b/src/vpux_compiler/src/dialect/IE/transforms/passes/forbid_four_bit_outputs.cpp
|
| 349 |
+
@@ -23,6 +23,8 @@ namespace vpux::IE {
|
| 350 |
+
#include "vpux/compiler/dialect/IE/passes.hpp.inc"
|
| 351 |
+
} // namespace vpux::IE
|
| 352 |
+
|
| 353 |
+
+#include <cstdlib>
|
| 354 |
+
+
|
| 355 |
+
using namespace vpux;
|
| 356 |
+
|
| 357 |
+
namespace {
|
| 358 |
+
@@ -41,6 +43,11 @@ private:
|
| 359 |
+
if (archKind != config::ArchKind::NPU37XX) {
|
| 360 |
+
return;
|
| 361 |
+
}
|
| 362 |
+
+ // onw: graphs without weights - the init schedule hands its 4-bit weights straight to the main schedule
|
| 363 |
+
+ // (slices of the model's packed codes, see weights_separation foldSubByteView); 4-bit inputs already work
|
| 364 |
+
+ if (moduleOp->hasAttr("VPU.OnwWeightless") && std::getenv("ONW_FORBID_4BIT_OUT") == nullptr) {
|
| 365 |
+
+ return;
|
| 366 |
+
+ }
|
| 367 |
+
auto netInfo = net::getNetworkInfo(moduleOp);
|
| 368 |
+
auto outputsInfo = netInfo.getOutputsDataInfo();
|
| 369 |
+
for (auto outputInfo : outputsInfo) {
|
| 370 |
+
diff --git a/src/vpux_compiler/src/dialect/IE/transforms/passes/merge_fully_connected.cpp b/src/vpux_compiler/src/dialect/IE/transforms/passes/merge_fully_connected.cpp
|
| 371 |
+
index baa9fec..029e104 100644
|
| 372 |
+
--- a/src/vpux_compiler/src/dialect/IE/transforms/passes/merge_fully_connected.cpp
|
| 373 |
+
+++ b/src/vpux_compiler/src/dialect/IE/transforms/passes/merge_fully_connected.cpp
|
| 374 |
+
@@ -3,6 +3,7 @@
|
| 375 |
+
// SPDX-License-Identifier: Apache-2.0
|
| 376 |
+
//
|
| 377 |
+
|
| 378 |
+
+#include <cstdlib>
|
| 379 |
+
#include "vpux/compiler/dialect/IE/IR/dialect.hpp"
|
| 380 |
+
#include "vpux/compiler/dialect/IE/IR/ops/convolution.hpp"
|
| 381 |
+
#include "vpux/compiler/dialect/IE/IR/ops/data_movement.hpp"
|
| 382 |
+
@@ -1516,6 +1517,13 @@ mlir::LogicalResult MergeFullyConnectedPass::initialize(mlir::MLIRContext* ctx)
|
| 383 |
+
}
|
| 384 |
+
|
| 385 |
+
void MergeFullyConnectedPass::safeRunOnFunc() {
|
| 386 |
+
+ // onw: ONW_WS_NO_MERGE_FC=1 - weightless compiles keep the unrolled group matmuls as they are. (Merged, the
|
| 387 |
+
+ // weight of k groups is a Concat of k group constants: VPU::WeightsSeparationInfo rewrites it into one chain on the
|
| 388 |
+
+ // OV constant again, so it goes weightless - before, it stayed in the blob as a second copy of the codes.)
|
| 389 |
+
+ if (auto mod = getOperation()->getParentOfType<mlir::ModuleOp>();
|
| 390 |
+
+ mod != nullptr && mod->hasAttr("VPU.OnwWeightless") && std::getenv("ONW_WS_NO_MERGE_FC") != nullptr) {
|
| 391 |
+
+ return;
|
| 392 |
+
+ }
|
| 393 |
+
auto& ctx = getContext();
|
| 394 |
+
auto func = getOperation();
|
| 395 |
+
|
| 396 |
+
diff --git a/src/vpux_compiler/src/dialect/IE/transforms/passes/merge_parallel_fully_connected.cpp b/src/vpux_compiler/src/dialect/IE/transforms/passes/merge_parallel_fully_connected.cpp
|
| 397 |
+
index c2c08f6..a3edc00 100644
|
| 398 |
+
--- a/src/vpux_compiler/src/dialect/IE/transforms/passes/merge_parallel_fully_connected.cpp
|
| 399 |
+
+++ b/src/vpux_compiler/src/dialect/IE/transforms/passes/merge_parallel_fully_connected.cpp
|
| 400 |
+
@@ -838,6 +838,14 @@ private:
|
| 401 |
+
//
|
| 402 |
+
|
| 403 |
+
void MergeParallelFullyConnectedPass::safeRunOnFunc() {
|
| 404 |
+
+ // onw: not when compiling without weights - the merged weights become one Const.Concat of several model constants,
|
| 405 |
+
+ // which the weights separation cannot trace back to the model's files (every merged weight stays in the graph:
|
| 406 |
+
+ // Ternary Bonsai 2 9B 1-token parts 331.8 MB weightless against 332.6 MB). ONW_WS_MERGE_FC=1 keeps the merge.
|
| 407 |
+
+ if (auto mod = getOperation()->getParentOfType<mlir::ModuleOp>();
|
| 408 |
+
+ mod != nullptr && mod->hasAttr("VPU.OnwWeightless") && std::getenv("ONW_WS_MERGE_FC") == nullptr) {
|
| 409 |
+
+ return;
|
| 410 |
+
+ }
|
| 411 |
+
+
|
| 412 |
+
auto& ctx = getContext();
|
| 413 |
+
auto func = getOperation();
|
| 414 |
+
|
| 415 |
diff --git a/src/vpux_compiler/src/dialect/IE/transforms/passes/swap_convert_with_reshape_kind_ops.cpp b/src/vpux_compiler/src/dialect/IE/transforms/passes/swap_convert_with_reshape_kind_ops.cpp
|
| 416 |
index d16690c..93f292f 100644
|
| 417 |
--- a/src/vpux_compiler/src/dialect/IE/transforms/passes/swap_convert_with_reshape_kind_ops.cpp
|
|
|
|
| 513 |
case VPU::MultiClusterStrategy::Clustering:
|
| 514 |
isStrategyCompatible = true;
|
| 515 |
diff --git a/src/vpux_compiler/src/dialect/VPU/transforms/passes/multi_cluster_strategy_assignment.cpp b/src/vpux_compiler/src/dialect/VPU/transforms/passes/multi_cluster_strategy_assignment.cpp
|
| 516 |
+
index adaca74..c5d7f27 100644
|
| 517 |
--- a/src/vpux_compiler/src/dialect/VPU/transforms/passes/multi_cluster_strategy_assignment.cpp
|
| 518 |
+++ b/src/vpux_compiler/src/dialect/VPU/transforms/passes/multi_cluster_strategy_assignment.cpp
|
| 519 |
@@ -5,6 +5,10 @@
|
|
|
|
| 527 |
#include "vpux/compiler/dialect/VPU/utils/cost_model/layer_vpunn_cost.hpp"
|
| 528 |
#include "vpux/compiler/dialect/VPU/utils/manual_strategy_utils.hpp"
|
| 529 |
#include "vpux/compiler/dialect/VPU/utils/multi_cluster_strategy_utils.hpp"
|
| 530 |
+
@@ -115,16 +119,28 @@ void MultiClusterStrategyAssignmentPass::safeRunOnFunc() {
|
| 531 |
+
cache.enableIfNecessary(clusteredOpCount > _clusteredOpThreshold);
|
| 532 |
+
|
| 533 |
+
auto& siblingAnalysis = getAnalysis<SiblingOpsAnalysis>();
|
| 534 |
+
+ VPU::OnwSiblingAnalysisScope onwSiblingScope(&siblingAnalysis); // (onw: see sibling_ops_analysis.hpp)
|
| 535 |
+
StrategyManager strategyManager(func, tileOp.getCount(), _enablePrefetchTiling, _mcOptimizationScope,
|
| 536 |
+
siblingAnalysis, std::move(layerCostModel), _enableTilingFullSearchSpace,
|
| 537 |
+
_log.nest());
|
| 538 |
_log.debug("Greedy Strategy Assignment");
|
| 539 |
auto enableMultiClusterForSWLayer =
|
| 540 |
config::getAvailableExecutor(module, config::ExecutorKind::SHAVE_ACT) != nullptr;
|
|
|
|
| 556 |
|
| 557 |
_log.trace("Remove Temporary Strategy");
|
| 558 |
strategyManager.removeTemporaryMulticlusterStrategy();
|
| 559 |
+
diff --git a/src/vpux_compiler/src/dialect/VPU/utils/overlap_distribution_utils.cpp b/src/vpux_compiler/src/dialect/VPU/utils/overlap_distribution_utils.cpp
|
| 560 |
+
index 62d2c5b..94ebf19 100644
|
| 561 |
+
--- a/src/vpux_compiler/src/dialect/VPU/utils/overlap_distribution_utils.cpp
|
| 562 |
+
+++ b/src/vpux_compiler/src/dialect/VPU/utils/overlap_distribution_utils.cpp
|
| 563 |
+
@@ -4,6 +4,7 @@
|
| 564 |
+
//
|
| 565 |
+
|
| 566 |
+
#include "vpux/compiler/dialect/VPU/utils/overlap_distribution_utils.hpp"
|
| 567 |
+
+#include <cstdlib>
|
| 568 |
+
#include "vpux/compiler/core/attributes/shape.hpp"
|
| 569 |
+
#include "vpux/compiler/core/tiling.hpp"
|
| 570 |
+
#include "vpux/compiler/dialect/VPU/IR/native_attributes/distribution_info.hpp"
|
| 571 |
+
@@ -18,6 +19,8 @@
|
| 572 |
+
#include <mlir/Dialect/Func/IR/FuncOps.h>
|
| 573 |
+
|
| 574 |
+
#include <stack>
|
| 575 |
+
+#include <unordered_map>
|
| 576 |
+
+#include <vector>
|
| 577 |
+
|
| 578 |
+
using namespace vpux;
|
| 579 |
+
using namespace VPU;
|
| 580 |
+
@@ -323,11 +326,106 @@ std::pair<Shape, Shape> getActualDataMemOffsetsAndShapes(VPU::SparseTensorType s
|
| 581 |
+
}
|
| 582 |
+
|
| 583 |
+
// Version with extension of halo
|
| 584 |
+
+static OverlapDistributionParams onwOverlapCompute(
|
| 585 |
+
+ NDTypeInterface tensorType, ArrayRef<VPU::ClusteredOpInterface> consumerSubgraph, const int64_t numClusters,
|
| 586 |
+
+ ArrayRef<int64_t> numTiles, const bool uniformDistributedSegments, const vpux::TileInfo& tileInfo,
|
| 587 |
+
+ const std::optional<ArrayRef<int64_t>> outputTensorMemoryNumTiles,
|
| 588 |
+
+ const std::optional<ArrayRef<int64_t>> alignment);
|
| 589 |
+
+
|
| 590 |
+
+namespace {
|
| 591 |
+
+// onw: the strategy search asks for the same overlapped params once per sibling and strategy check - a 2-bit Bonsai
|
| 592 |
+
+// matmul is split into one conv per weight group, so ~20 siblings each recomputed the whole group (94% of the strategy
|
| 593 |
+
+// pass). The result depends only on the values in the key, so it is cached while a pass publishes its sibling analysis
|
| 594 |
+
+// (OnwSiblingAnalysisScope clears it on entry and exit). ONW_NO_OVERLAP_CACHE=1: as before.
|
| 595 |
+
+struct OnwKeyHash {
|
| 596 |
+
+ size_t operator()(const std::vector<int64_t>& k) const {
|
| 597 |
+
+ return llvm::hash_combine_range(k.begin(), k.end());
|
| 598 |
+
+ }
|
| 599 |
+
+};
|
| 600 |
+
+thread_local std::unordered_map<std::vector<int64_t>, OverlapDistributionParams, OnwKeyHash> onwOverlapCache;
|
| 601 |
+
+// kernel / strides / pads per NCE op: getKernelSizeVal builds the filter shape from attributes each time (more than
|
| 602 |
+
+// half of the key cost); the strategy pass only sets strategies, so they stay fixed while the scope is active
|
| 603 |
+
+thread_local llvm::DenseMap<mlir::Operation*, SmallVector<int64_t, 12>> onwNceGeometry;
|
| 604 |
+
+const bool onwOverlapCacheOff = std::getenv("ONW_NO_OVERLAP_CACHE") != nullptr;
|
| 605 |
+
+// ONW_NO_SIBLING_VIEW=1: siblings / consumers copied out of the analysis as before
|
| 606 |
+
+const bool onwSiblingViewOn = std::getenv("ONW_NO_SIBLING_VIEW") == nullptr;
|
| 607 |
+
+} // namespace
|
| 608 |
+
+
|
| 609 |
+
+void vpux::VPU::onwClearOverlapCache() {
|
| 610 |
+
+ onwOverlapCache.clear();
|
| 611 |
+
+ onwNceGeometry.clear();
|
| 612 |
+
+}
|
| 613 |
+
+
|
| 614 |
+
OverlapDistributionParams vpux::VPU::getOverlappedDistributionParameters(
|
| 615 |
+
NDTypeInterface tensorType, ArrayRef<VPU::ClusteredOpInterface> consumerSubgraph, const int64_t numClusters,
|
| 616 |
+
ArrayRef<int64_t> numTiles, const bool uniformDistributedSegments, const vpux::TileInfo& tileInfo,
|
| 617 |
+
const std::optional<ArrayRef<int64_t>> outputTensorMemoryNumTiles,
|
| 618 |
+
const std::optional<ArrayRef<int64_t>> alignment) {
|
| 619 |
+
+ if (onwCurrentSiblingAnalysis == nullptr || onwOverlapCacheOff || tensorType == nullptr) {
|
| 620 |
+
+ return onwOverlapCompute(tensorType, consumerSubgraph, numClusters, numTiles, uniformDistributedSegments,
|
| 621 |
+
+ tileInfo, outputTensorMemoryNumTiles, alignment);
|
| 622 |
+
+ }
|
| 623 |
+
+ std::vector<int64_t> key;
|
| 624 |
+
+ key.reserve(16 + 12 * consumerSubgraph.size());
|
| 625 |
+
+ key.push_back(reinterpret_cast<int64_t>(tensorType.getAsOpaquePointer()));
|
| 626 |
+
+ key.push_back(numClusters);
|
| 627 |
+
+ key.push_back(uniformDistributedSegments ? 1 : 0);
|
| 628 |
+
+ key.push_back(static_cast<int64_t>(numTiles.size()));
|
| 629 |
+
+ key.insert(key.end(), numTiles.begin(), numTiles.end());
|
| 630 |
+
+ key.push_back(outputTensorMemoryNumTiles.has_value() ? static_cast<int64_t>(outputTensorMemoryNumTiles->size())
|
| 631 |
+
+ : -1);
|
| 632 |
+
+ if (outputTensorMemoryNumTiles.has_value()) {
|
| 633 |
+
+ key.insert(key.end(), outputTensorMemoryNumTiles->begin(), outputTensorMemoryNumTiles->end());
|
| 634 |
+
+ }
|
| 635 |
+
+ key.push_back(alignment.has_value() ? static_cast<int64_t>(alignment->size()) : -1);
|
| 636 |
+
+ if (alignment.has_value()) {
|
| 637 |
+
+ key.insert(key.end(), alignment->begin(), alignment->end());
|
| 638 |
+
+ }
|
| 639 |
+
+ bool anyNce = false;
|
| 640 |
+
+ for (auto clusteredOp : consumerSubgraph) {
|
| 641 |
+
+ auto nceOp = mlir::dyn_cast<VPU::NCEOpInterface>(clusteredOp.getOperation());
|
| 642 |
+
+ if (nceOp == nullptr) {
|
| 643 |
+
+ continue;
|
| 644 |
+
+ }
|
| 645 |
+
+ anyNce = true;
|
| 646 |
+
+ auto [geo, fresh] = onwNceGeometry.try_emplace(nceOp.getOperation());
|
| 647 |
+
+ if (fresh) {
|
| 648 |
+
+ const auto kernel = nceOp.getKernelSizeVal();
|
| 649 |
+
+ const auto strides = nceOp.getStridesVal();
|
| 650 |
+
+ const auto pads = VPU::Padding::getClassFromAttr(nceOp.getPad());
|
| 651 |
+
+ auto& g = geo->second;
|
| 652 |
+
+ g.push_back(-7);
|
| 653 |
+
+ g.push_back(static_cast<int64_t>(kernel.size()));
|
| 654 |
+
+ g.append(kernel.begin(), kernel.end());
|
| 655 |
+
+ g.push_back(static_cast<int64_t>(strides.size()));
|
| 656 |
+
+ g.append(strides.begin(), strides.end());
|
| 657 |
+
+ g.push_back(pads.getLeftPad());
|
| 658 |
+
+ g.push_back(pads.getRightPad());
|
| 659 |
+
+ g.push_back(pads.getTopPad());
|
| 660 |
+
+ g.push_back(pads.getBottomPad());
|
| 661 |
+
+ }
|
| 662 |
+
+ key.insert(key.end(), geo->second.begin(), geo->second.end());
|
| 663 |
+
+ key.push_back(reinterpret_cast<int64_t>(nceOp->getOperand(0).getType().getAsOpaquePointer()));
|
| 664 |
+
+ }
|
| 665 |
+
+ if (anyNce) {
|
| 666 |
+
+ const auto clusteringAxis = VPU::getDistributedTilingAxis(numTiles);
|
| 667 |
+
+ const auto tileOffset = !tileInfo.offsets.empty() ? tileInfo.offsets[Dim(clusteringAxis)] : 0;
|
| 668 |
+
+ key.push_back(tileOffset != 0 ? 1 : 0);
|
| 669 |
+
+ }
|
| 670 |
+
+ if (auto it = onwOverlapCache.find(key); it != onwOverlapCache.end()) {
|
| 671 |
+
+ return it->second;
|
| 672 |
+
+ }
|
| 673 |
+
+ auto result = onwOverlapCompute(tensorType, consumerSubgraph, numClusters, numTiles, uniformDistributedSegments,
|
| 674 |
+
+ tileInfo, outputTensorMemoryNumTiles, alignment);
|
| 675 |
+
+ onwOverlapCache.emplace(std::move(key), result);
|
| 676 |
+
+ return result;
|
| 677 |
+
+}
|
| 678 |
+
+
|
| 679 |
+
+static OverlapDistributionParams onwOverlapCompute(
|
| 680 |
+
+ NDTypeInterface tensorType, ArrayRef<VPU::ClusteredOpInterface> consumerSubgraph, const int64_t numClusters,
|
| 681 |
+
+ ArrayRef<int64_t> numTiles, const bool uniformDistributedSegments, const vpux::TileInfo& tileInfo,
|
| 682 |
+
+ const std::optional<ArrayRef<int64_t>> outputTensorMemoryNumTiles,
|
| 683 |
+
+ const std::optional<ArrayRef<int64_t>> alignment) {
|
| 684 |
+
VPUX_THROW_WHEN(tensorType == nullptr, "getOverlappedDistributionParameters: tensorType cannot be nullptr");
|
| 685 |
+
|
| 686 |
+
auto neutralKernel = SmallVector<int64_t>{1, 1};
|
| 687 |
+
@@ -701,18 +799,23 @@ OverlapDistributionParams vpux::VPU::getActivationOverlappedParams(VPU::Clustere
|
| 688 |
+
auto sparseType = mlir::dyn_cast<vpux::VPU::SparseTensorType>(clusteredOp->getOperand(0).getType());
|
| 689 |
+
const bool isSparseTypeInputWithSeTable = (sparseType != nullptr) && (sparseType.getSeAttr() != nullptr);
|
| 690 |
+
SmallVector<VPU::ClusteredOpInterface> siblingSubgraph{};
|
| 691 |
+
+ ArrayRef<VPU::ClusteredOpInterface> siblingView;
|
| 692 |
+
if (isSparseTypeInputWithSeTable) {
|
| 693 |
+
siblingSubgraph.push_back(clusteredOp);
|
| 694 |
+
+ siblingView = siblingSubgraph;
|
| 695 |
+
+ } else if (onwSiblingViewOn) {
|
| 696 |
+
+ siblingView = siblingsAnalysis.onwSiblingsView(clusteredOp);
|
| 697 |
+
} else {
|
| 698 |
+
auto siblings = siblingsAnalysis.getSiblings(clusteredOp);
|
| 699 |
+
siblingSubgraph.append(siblings.begin(), siblings.end());
|
| 700 |
+
+ siblingView = siblingSubgraph;
|
| 701 |
+
}
|
| 702 |
+
|
| 703 |
+
const auto clusteringAxis = VPU::getDistributedTilingAxis(activationTensorNumTiles);
|
| 704 |
+
return getOverlappedDistributionParameters(
|
| 705 |
+
(inputType != nullptr) ? inputType
|
| 706 |
+
: mlir::cast<vpux::NDTypeInterface>(clusteredOp->getOperand(0).getType()),
|
| 707 |
+
- siblingSubgraph, activationTensorNumTiles[clusteringAxis], activationTensorNumTiles,
|
| 708 |
+
+ siblingView, activationTensorNumTiles[clusteringAxis], activationTensorNumTiles,
|
| 709 |
+
uniformDistributedSegments, tileInfo);
|
| 710 |
+
}
|
| 711 |
+
|
| 712 |
+
@@ -920,12 +1023,20 @@ OverlapDistributionParams vpux::VPU::getOutputOverlappedParams(
|
| 713 |
+
return getOutputOverlappedParamsNoHalo(clusteredOp, outputTensorNumTiles);
|
| 714 |
+
}
|
| 715 |
+
|
| 716 |
+
- auto consumers = siblingsAnalysis.getConsumers(clusteredOp);
|
| 717 |
+
+ SmallVector<VPU::ClusteredOpInterface> consumerCopy;
|
| 718 |
+
+ ArrayRef<VPU::ClusteredOpInterface> consumerView;
|
| 719 |
+
+ if (onwSiblingViewOn) {
|
| 720 |
+
+ consumerView = siblingsAnalysis.onwConsumersView(clusteredOp);
|
| 721 |
+
+ } else {
|
| 722 |
+
+ auto consumers = siblingsAnalysis.getConsumers(clusteredOp);
|
| 723 |
+
+ consumerCopy.assign(consumers.begin(), consumers.end());
|
| 724 |
+
+ consumerView = consumerCopy;
|
| 725 |
+
+ }
|
| 726 |
+
const auto clusteringAxis = VPU::getDistributedTilingAxis(outputTensorNumTiles);
|
| 727 |
+
return getOverlappedDistributionParameters(
|
| 728 |
+
(outputType != nullptr) ? outputType
|
| 729 |
+
: mlir::cast<vpux::NDTypeInterface>(clusteredOp->getResult(0).getType()),
|
| 730 |
+
- SmallVector<VPU::ClusteredOpInterface>(consumers.begin(), consumers.end()),
|
| 731 |
+
+ consumerView,
|
| 732 |
+
outputTensorNumTiles[clusteringAxis], outputTensorNumTiles, uniformDistributedSegments, tileInfo,
|
| 733 |
+
outputTensorMemoryNumTiles, alignment);
|
| 734 |
+
}
|
| 735 |
+
@@ -941,6 +1052,10 @@ OverlapDistributionParams vpux::VPU::getOutputOverlappedParams(
|
| 736 |
+
return getOutputOverlappedParamsNoHalo(clusteredOp, outputTensorNumTiles);
|
| 737 |
+
}
|
| 738 |
+
|
| 739 |
+
+ if (auto* shared = onwCurrentSiblingAnalysis; shared != nullptr && std::getenv("ONW_NO_SIBLING_CACHE") == nullptr) {
|
| 740 |
+
+ return getOutputOverlappedParams(clusteredOp, outputTensorNumTiles, uniformDistributedSegments, outputType,
|
| 741 |
+
+ tileInfo, *shared, outputTensorMemoryNumTiles, alignment);
|
| 742 |
+
+ }
|
| 743 |
+
auto siblingsAnalysis = SiblingOpsAnalysis(clusteredOp);
|
| 744 |
+
return getOutputOverlappedParams(clusteredOp, outputTensorNumTiles, uniformDistributedSegments, outputType,
|
| 745 |
+
tileInfo, siblingsAnalysis, outputTensorMemoryNumTiles, alignment);
|
| 746 |
+
diff --git a/src/vpux_compiler/src/dialect/VPU/utils/sibling_ops_analysis.cpp b/src/vpux_compiler/src/dialect/VPU/utils/sibling_ops_analysis.cpp
|
| 747 |
+
index c5f0be3..c28612d 100644
|
| 748 |
+
--- a/src/vpux_compiler/src/dialect/VPU/utils/sibling_ops_analysis.cpp
|
| 749 |
+
+++ b/src/vpux_compiler/src/dialect/VPU/utils/sibling_ops_analysis.cpp
|
| 750 |
+
@@ -8,6 +8,8 @@
|
| 751 |
+
|
| 752 |
+
namespace vpux::VPU {
|
| 753 |
+
|
| 754 |
+
+thread_local SiblingOpsAnalysis* onwCurrentSiblingAnalysis = nullptr;
|
| 755 |
+
+
|
| 756 |
+
// Empty constructor to satisfy getAnalysis API requirements.
|
| 757 |
+
SiblingOpsAnalysis::SiblingOpsAnalysis(mlir::Operation*) {
|
| 758 |
+
}
|
| 759 |
+
@@ -16,17 +18,74 @@ std::set<ClusteredOpInterface> SiblingOpsAnalysis::lookupOpSiblings(mlir::Operat
|
| 760 |
+
if (op == nullptr) {
|
| 761 |
+
return {};
|
| 762 |
+
}
|
| 763 |
+
- if (auto clusteredOp = mlir::dyn_cast<VPU::ClusteredOpInterface>(op)) {
|
| 764 |
+
- for (const auto& siblingGroup : _siblingGroups) {
|
| 765 |
+
- if (siblingGroup.find(clusteredOp) != siblingGroup.end()) {
|
| 766 |
+
- return siblingGroup;
|
| 767 |
+
- }
|
| 768 |
+
- }
|
| 769 |
+
+ if (auto it = _onwGroupOf.find(op); it != _onwGroupOf.end()) {
|
| 770 |
+
+ return _siblingGroups[it->second];
|
| 771 |
+
}
|
| 772 |
+
-
|
| 773 |
+
return {};
|
| 774 |
+
}
|
| 775 |
+
|
| 776 |
+
+void SiblingOpsAnalysis::addGroup(mlir::Operation* queried, std::set<ClusteredOpInterface> group) {
|
| 777 |
+
+ const size_t idx = _siblingGroups.size();
|
| 778 |
+
+ for (auto member : group) { // (by value: getOperation is not const)
|
| 779 |
+
+ _onwGroupOf.try_emplace(member.getOperation(), idx);
|
| 780 |
+
+ }
|
| 781 |
+
+ if (queried != nullptr) {
|
| 782 |
+
+ _onwGroupOf.try_emplace(queried, idx);
|
| 783 |
+
+ }
|
| 784 |
+
+ _onwGroupVecs.emplace_back(group.begin(), group.end());
|
| 785 |
+
+ _siblingGroups.emplace_back(std::move(group));
|
| 786 |
+
+}
|
| 787 |
+
+
|
| 788 |
+
+// (eager analyses are read from many threads at once - TilingStrategyAssignment's workers: their copies go into a
|
| 789 |
+
+// buffer of the calling thread; a shared member buffer was overwritten by other threads -> access violations)
|
| 790 |
+
+static thread_local SmallVector<ClusteredOpInterface> onwThreadScratch;
|
| 791 |
+
+
|
| 792 |
+
+ArrayRef<ClusteredOpInterface> SiblingOpsAnalysis::onwSiblingsView(ClusteredOpInterface clusteredOp) {
|
| 793 |
+
+ if (!_onwLazy) {
|
| 794 |
+
+ auto s = getSiblings(clusteredOp);
|
| 795 |
+
+ onwThreadScratch.assign(s.begin(), s.end());
|
| 796 |
+
+ return onwThreadScratch;
|
| 797 |
+
+ }
|
| 798 |
+
+ auto it = _onwGroupOf.find(clusteredOp.getOperation());
|
| 799 |
+
+ if (it == _onwGroupOf.end() || _siblingGroups[it->second].empty()) {
|
| 800 |
+
+ // as getSiblings: compute (an empty group is computed again, which gives an empty group again)
|
| 801 |
+
+ addGroup(clusteredOp.getOperation(), getSiblingOps(clusteredOp));
|
| 802 |
+
+ return _onwGroupVecs.back();
|
| 803 |
+
+ }
|
| 804 |
+
+ return _onwGroupVecs[it->second];
|
| 805 |
+
+}
|
| 806 |
+
+
|
| 807 |
+
+ArrayRef<ClusteredOpInterface> SiblingOpsAnalysis::onwConsumersView(ClusteredOpInterface clusteredOp) {
|
| 808 |
+
+ if (!_onwLazy) {
|
| 809 |
+
+ auto s = getConsumers(clusteredOp);
|
| 810 |
+
+ onwThreadScratch.assign(s.begin(), s.end());
|
| 811 |
+
+ return onwThreadScratch;
|
| 812 |
+
+ }
|
| 813 |
+
+ if (auto it = _onwConsumerGroup.find(clusteredOp.getOperation()); it != _onwConsumerGroup.end()) {
|
| 814 |
+
+ return _onwGroupVecs[it->second];
|
| 815 |
+
+ }
|
| 816 |
+
+ if (_consumers.contains(clusteredOp)) {
|
| 817 |
+
+ // filled by getConsumers before: answer with that copy
|
| 818 |
+
+ const auto& s = _consumers.at(clusteredOp);
|
| 819 |
+
+ onwThreadScratch.assign(s.begin(), s.end());
|
| 820 |
+
+ return onwThreadScratch;
|
| 821 |
+
+ }
|
| 822 |
+
+ auto consumerOp = getConsumerOp(clusteredOp);
|
| 823 |
+
+ if (consumerOp == nullptr) {
|
| 824 |
+
+ return {};
|
| 825 |
+
+ }
|
| 826 |
+
+ size_t idx;
|
| 827 |
+
+ auto it = _onwGroupOf.find(consumerOp);
|
| 828 |
+
+ if (it != _onwGroupOf.end() && !_siblingGroups[it->second].empty()) {
|
| 829 |
+
+ idx = it->second;
|
| 830 |
+
+ } else {
|
| 831 |
+
+ addGroup(consumerOp, getSiblingOps(consumerOp));
|
| 832 |
+
+ idx = _siblingGroups.size() - 1;
|
| 833 |
+
+ }
|
| 834 |
+
+ _onwConsumerGroup.try_emplace(clusteredOp.getOperation(), idx);
|
| 835 |
+
+ return _onwGroupVecs[idx];
|
| 836 |
+
+}
|
| 837 |
+
+
|
| 838 |
+
mlir::Operation* SiblingOpsAnalysis::getConsumerOp(ClusteredOpInterface clusteredOp) {
|
| 839 |
+
if (isPassthroughOp(clusteredOp.getOperation())) {
|
| 840 |
+
// For passthrough ops, ensure input and output tensors use the same pool of ops to
|
| 841 |
+
@@ -47,7 +106,7 @@ std::set<ClusteredOpInterface> SiblingOpsAnalysis::getSiblings(ClusteredOpInterf
|
| 842 |
+
auto siblings = lookupOpSiblings(clusteredOp);
|
| 843 |
+
if (siblings.empty()) {
|
| 844 |
+
siblings = getSiblingOps(clusteredOp);
|
| 845 |
+
- _siblingGroups.emplace_back(siblings);
|
| 846 |
+
+ addGroup(clusteredOp.getOperation(), siblings);
|
| 847 |
+
}
|
| 848 |
+
return siblings;
|
| 849 |
+
}
|
| 850 |
+
@@ -63,13 +122,14 @@ std::set<ClusteredOpInterface> SiblingOpsAnalysis::getConsumers(ClusteredOpInter
|
| 851 |
+
auto consumers = lookupOpSiblings(consumerOp);
|
| 852 |
+
if (consumers.empty()) {
|
| 853 |
+
consumers = getSiblingOps(consumerOp);
|
| 854 |
+
- _siblingGroups.emplace_back(consumers);
|
| 855 |
+
+ addGroup(consumerOp, consumers);
|
| 856 |
+
}
|
| 857 |
+
_consumers.insert(std::make_pair(clusteredOp, consumers));
|
| 858 |
+
return consumers;
|
| 859 |
+
}
|
| 860 |
+
|
| 861 |
+
EagerSiblingOpsAnalysis::EagerSiblingOpsAnalysis(mlir::func::FuncOp func): SiblingOpsAnalysis(func) {
|
| 862 |
+
+ _onwLazy = false;
|
| 863 |
+
// Skip siblings for architectures that do not support halo overlap to save some compile time
|
| 864 |
+
if (!outputOverlappedParamsIsHaloSupported(func)) {
|
| 865 |
+
return;
|
| 866 |
+
@@ -77,7 +137,7 @@ EagerSiblingOpsAnalysis::EagerSiblingOpsAnalysis(mlir::func::FuncOp func): Sibli
|
| 867 |
+
func->walk([&](VPU::ClusteredOpInterface clusteredOp) {
|
| 868 |
+
if (lookupOpSiblings(clusteredOp.getOperation()).empty()) {
|
| 869 |
+
auto opSiblings = getSiblingOps(clusteredOp.getOperation());
|
| 870 |
+
- _siblingGroups.emplace_back(opSiblings);
|
| 871 |
+
+ addGroup(clusteredOp.getOperation(), opSiblings);
|
| 872 |
+
}
|
| 873 |
+
auto consumerOp = getConsumerOp(clusteredOp);
|
| 874 |
+
if (consumerOp == nullptr) {
|
| 875 |
+
@@ -86,7 +146,7 @@ EagerSiblingOpsAnalysis::EagerSiblingOpsAnalysis(mlir::func::FuncOp func): Sibli
|
| 876 |
+
auto consumerSiblings = lookupOpSiblings(consumerOp);
|
| 877 |
+
if (consumerSiblings.empty()) {
|
| 878 |
+
consumerSiblings = getSiblingOps(consumerOp);
|
| 879 |
+
- _siblingGroups.emplace_back(consumerSiblings);
|
| 880 |
+
+ addGroup(consumerOp, consumerSiblings);
|
| 881 |
+
}
|
| 882 |
+
_consumers.insert(std::make_pair(clusteredOp, consumerSiblings));
|
| 883 |
+
});
|
| 884 |
diff --git a/src/vpux_compiler/src/dialect/VPU/utils/strategy_manager/multi_cluster_strategy_utils.cpp b/src/vpux_compiler/src/dialect/VPU/utils/strategy_manager/multi_cluster_strategy_utils.cpp
|
| 885 |
index 4bfec49..26e3194 100644
|
| 886 |
--- a/src/vpux_compiler/src/dialect/VPU/utils/strategy_manager/multi_cluster_strategy_utils.cpp
|
|
|
|
| 1313 |
return mlir::success();
|
| 1314 |
}
|
| 1315 |
diff --git a/src/vpux_compiler/src/dialect/VPU/utils/vertical_fusion/v2/merge_vf_region_rewriter.cpp b/src/vpux_compiler/src/dialect/VPU/utils/vertical_fusion/v2/merge_vf_region_rewriter.cpp
|
| 1316 |
+
index f3d8660..2d4e4fb 100644
|
| 1317 |
--- a/src/vpux_compiler/src/dialect/VPU/utils/vertical_fusion/v2/merge_vf_region_rewriter.cpp
|
| 1318 |
+++ b/src/vpux_compiler/src/dialect/VPU/utils/vertical_fusion/v2/merge_vf_region_rewriter.cpp
|
| 1319 |
@@ -28,6 +28,43 @@
|
|
|
|
| 1466 |
return std::nullopt;
|
| 1467 |
}
|
| 1468 |
|
| 1469 |
+
@@ -543,23 +630,108 @@ bool MergeVFRegionRewriter::checkVFCostFunction(VPU::VerticalFusionOp prevOp, VP
|
| 1470 |
return true;
|
| 1471 |
}
|
| 1472 |
|
|
|
|
| 1494 |
+llvm::hash_code onwHashVF(VPU::VerticalFusionOp op) {
|
| 1495 |
+ llvm::hash_code h = llvm::hash_value(op->getAttrDictionary().getAsOpaquePointer());
|
| 1496 |
+ for (auto v : op->getOperands()) {
|
| 1497 |
+
+ h = llvm::hash_combine(h, v ? onwHashType(v.getType()) : llvm::hash_code(0));
|
| 1498 |
+ }
|
| 1499 |
+ op.getBody()->walk([&](mlir::Operation* o) {
|
| 1500 |
+ h = llvm::hash_combine(h, o->getName().getAsOpaquePointer(), o->getAttrDictionary().getAsOpaquePointer());
|
| 1501 |
+
+ for (auto v : o->getOperands()) { // (a null operand: an op being rewritten)
|
| 1502 |
+
+ h = llvm::hash_combine(h, v ? onwHashType(v.getType()) : llvm::hash_code(0));
|
| 1503 |
+ }
|
| 1504 |
+ for (auto t : o->getResultTypes()) {
|
| 1505 |
+ h = llvm::hash_combine(h, onwHashType(t));
|
|
|
|
| 1528 |
_log.trace("Starting vertical fusion for region with VerticalFusionOp {0} at location {1}", vfOp, vfOp->getLoc());
|
| 1529 |
if (vfOp.getIsManualConfigured()) {
|
| 1530 |
_log.trace("Skipping vertical fusion for manually configured VerticalFusionOp at location {0}", vfOp->getLoc());
|
| 1531 |
+
return mlir::failure();
|
| 1532 |
+
}
|
| 1533 |
+
+ // onw: regions with split-weight-table convolutions (ONW_NEW_WT) are not merged - this build's merge leaves their
|
| 1534 |
+
+ // scale / table operands unset (null) in the candidate regions
|
| 1535 |
+
+ {
|
| 1536 |
+
+ bool splitTable = false;
|
| 1537 |
+
+ vfOp.getBody()->walk([&](VPU::NCEConvolutionOp conv) {
|
| 1538 |
+
+ splitTable |= conv.getWeightTableScale() != nullptr;
|
| 1539 |
+
+ });
|
| 1540 |
+
+ for (auto operand : vfOp->getOperands()) {
|
| 1541 |
+
+ if (auto prev = operand.getDefiningOp<VPU::VerticalFusionOp>()) {
|
| 1542 |
+
+ prev.getBody()->walk([&](VPU::NCEConvolutionOp conv) {
|
| 1543 |
+
+ splitTable |= conv.getWeightTableScale() != nullptr;
|
| 1544 |
+
+ });
|
| 1545 |
+
+ }
|
| 1546 |
+
+ }
|
| 1547 |
+
+ if (splitTable) {
|
| 1548 |
+
+ return mlir::failure();
|
| 1549 |
+
+ }
|
| 1550 |
+
+ }
|
| 1551 |
|
| 1552 |
VPU::VerticalFusionOp vfBlock = nullptr;
|
| 1553 |
VPU::VerticalFusionOp parentVFOp = nullptr;
|
|
|
|
| 1575 |
|
| 1576 |
_log.trace("Analyzing vertical fusion region with parent VerticalFusionOp {0} at location {1}", parentVFOp,
|
| 1577 |
parentVFOp->getLoc());
|
| 1578 |
+
@@ -580,7 +752,10 @@ mlir::LogicalResult MergeVFRegionRewriter::matchAndRewrite(VPU::VerticalFusionOp
|
| 1579 |
continue;
|
| 1580 |
}
|
| 1581 |
|
|
|
|
| 1587 |
_log.trace("Merged subgraph candidate {0}", vfBlock.getLoc());
|
| 1588 |
// `findVFCase` internally compares operations using IR order which is calculated "on the fly" in e.g.
|
| 1589 |
// `isBeforeInBlock()`
|
| 1590 |
+
@@ -590,15 +765,34 @@ mlir::LogicalResult MergeVFRegionRewriter::matchAndRewrite(VPU::VerticalFusionOp
|
| 1591 |
// As `findVFCase` is expected to not modify the IR, what can be done is an eager recalculation
|
| 1592 |
// of the operation orders that would ensure later calls to e.g. `isBeforeInBlock()` become concurrent
|
| 1593 |
// reads instead of concurrent writes (which is no longer a data race).
|
|
|
|
| 1626 |
if (checkOtherVFInput(vfOp, parentVFOp)) {
|
| 1627 |
continue;
|
| 1628 |
}
|
| 1629 |
+
@@ -614,7 +808,10 @@ mlir::LogicalResult MergeVFRegionRewriter::matchAndRewrite(VPU::VerticalFusionOp
|
| 1630 |
}
|
| 1631 |
|
| 1632 |
_log.trace("Merged subgraph {0}", vfBlock->getLoc());
|
|
|
|
| 1638 |
|
| 1639 |
return mlir::success();
|
| 1640 |
}
|
| 1641 |
+
diff --git a/src/vpux_compiler/src/dialect/VPU/utils/vertical_fusion/v2/vertical_fusion_config.cpp b/src/vpux_compiler/src/dialect/VPU/utils/vertical_fusion/v2/vertical_fusion_config.cpp
|
| 1642 |
+
index d25b349..e5fef7c 100644
|
| 1643 |
+
--- a/src/vpux_compiler/src/dialect/VPU/utils/vertical_fusion/v2/vertical_fusion_config.cpp
|
| 1644 |
+
+++ b/src/vpux_compiler/src/dialect/VPU/utils/vertical_fusion/v2/vertical_fusion_config.cpp
|
| 1645 |
+
@@ -3,6 +3,7 @@
|
| 1646 |
+
// SPDX-License-Identifier: Apache-2.0
|
| 1647 |
+
//
|
| 1648 |
+
|
| 1649 |
+
+#include <cstdlib>
|
| 1650 |
+
#include "vpux/compiler/dialect/VPU/utils/vertical_fusion/v2/vertical_fusion_config.hpp"
|
| 1651 |
+
#include "vpux/compiler/dialect/VPU/utils/distributed_tensor_utils.hpp"
|
| 1652 |
+
#include "vpux/compiler/dialect/VPU/utils/hash_utils.hpp"
|
| 1653 |
+
@@ -167,6 +168,16 @@ void VFConfig::init() {
|
| 1654 |
+
|
| 1655 |
+
if (_inputOps.empty()) {
|
| 1656 |
+
auto operations = getVFOperations();
|
| 1657 |
+
+ if (std::getenv("ONW_DEBUG_NULLOP") != nullptr) { // onw: report ops with a null operand
|
| 1658 |
+
+ for (auto* operation : operations) {
|
| 1659 |
+
+ for (unsigned i = 0; i < operation->getNumOperands(); ++i) {
|
| 1660 |
+
+ if (!operation->getOperand(i)) {
|
| 1661 |
+
+ llvm::errs() << "ONW null operand " << i << " of " << operation->getName() << " at "
|
| 1662 |
+
+ << operation->getLoc() << "\n";
|
| 1663 |
+
+ }
|
| 1664 |
+
+ }
|
| 1665 |
+
+ }
|
| 1666 |
+
+ }
|
| 1667 |
+
const auto allOperandsInputs = [&](auto* current) -> bool {
|
| 1668 |
+
return llvm::all_of(current->getOperands(), [&](mlir::Value operand) {
|
| 1669 |
+
return mlir::dyn_cast<mlir::BlockArgument>(operand) != nullptr ||
|
| 1670 |
+
@@ -197,7 +208,8 @@ void VFConfig::init() {
|
| 1671 |
+
break;
|
| 1672 |
+
}
|
| 1673 |
+
|
| 1674 |
+
- parent = parent->getOperand(0).getDefiningOp();
|
| 1675 |
+
+ // onw: an op without operands (a constant feeding the split weight table's scale)
|
| 1676 |
+
+ parent = parent->getNumOperands() > 0 ? parent->getOperand(0).getDefiningOp() : nullptr;
|
| 1677 |
+
}
|
| 1678 |
+
}
|
| 1679 |
+
}
|
| 1680 |
diff --git a/src/vpux_compiler/src/dialect/VPU/utils/weights_separation.cpp b/src/vpux_compiler/src/dialect/VPU/utils/weights_separation.cpp
|
| 1681 |
+
index 35a6c00..cb47654 100644
|
| 1682 |
--- a/src/vpux_compiler/src/dialect/VPU/utils/weights_separation.cpp
|
| 1683 |
+++ b/src/vpux_compiler/src/dialect/VPU/utils/weights_separation.cpp
|
| 1684 |
@@ -4,6 +4,7 @@
|
|
|
|
| 1689 |
#include "vpux/compiler/core/force_link_macros.hpp"
|
| 1690 |
#include "vpux/compiler/dialect/IE/IR/ops/data_movement.hpp"
|
| 1691 |
#include "vpux/compiler/dialect/IE/IR/ops/data_type.hpp"
|
| 1692 |
+
@@ -128,10 +129,24 @@ bool isTrivialForWeightsSeparation(Const::DeclareOp constOp, bool skipViewLikeOn
|
| 1693 |
+
bool isSuitableForWeightlessCompilation(Const::DeclareOp constOp, bool skipViewLikeOnly) {
|
| 1694 |
+
// ignore all non-OV constants
|
| 1695 |
+
if (!Const::isOpenVINOConstant(constOp)) {
|
| 1696 |
+
+ if (std::getenv("ONW_DEBUG_WS") != nullptr) {
|
| 1697 |
+
+ const auto bt = mlir::cast<NDTypeInterface>(constOp.getContentAttr().getBaseContent().getType());
|
| 1698 |
+
+ if (bt.getTotalAllocSize().count() >= (1 << 14)) {
|
| 1699 |
+
+ llvm::errs() << "ONW ws non-OV constant: " << bt << " " << constOp.getLoc() << char(10);
|
| 1700 |
+
+ }
|
| 1701 |
+
+ }
|
| 1702 |
+
return false;
|
| 1703 |
+
}
|
| 1704 |
+
|
| 1705 |
+
if (isTrivialForWeightsSeparation(constOp, skipViewLikeOnly)) {
|
| 1706 |
+
+ if (std::getenv("ONW_DEBUG_WS") != nullptr) {
|
| 1707 |
+
+ const auto bt = mlir::cast<NDTypeInterface>(constOp.getContentAttr().getBaseContent().getType());
|
| 1708 |
+
+ if (bt.getTotalAllocSize().count() >= (1 << 14)) {
|
| 1709 |
+
+ llvm::errs() << "ONW ws trivial: " << bt << " splat=" << constOp.getContentAttr().isSplat()
|
| 1710 |
+
+ << " skipViewLikeOnly=" << skipViewLikeOnly << " n="
|
| 1711 |
+
+ << constOp.getContentAttr().getTransformations().size() << char(10);
|
| 1712 |
+
+ }
|
| 1713 |
+
+ }
|
| 1714 |
+
return false;
|
| 1715 |
+
}
|
| 1716 |
+
|
| 1717 |
+
@@ -160,7 +175,16 @@ bool isSuitableForWeightlessCompilation(Const::DeclareOp constOp, bool skipViewL
|
| 1718 |
+
return conversions::isSupportedTransformation(baseType, transformations.back());
|
| 1719 |
+
};
|
| 1720 |
+
|
| 1721 |
+
- return hasOnlySupportedTransformations(constOp);
|
| 1722 |
+
+ const bool supported = hasOnlySupportedTransformations(constOp);
|
| 1723 |
+
+ if (!supported && std::getenv("ONW_DEBUG_WS") != nullptr) {
|
| 1724 |
+
+ llvm::errs() << "ONW ws rejected: " << constOp.getContentAttr().getBaseContent().getType() << " "
|
| 1725 |
+
+ << constOp.getContentAttr().getTransformations().size() << " transformations:";
|
| 1726 |
+
+ for (auto t : constOp.getContentAttr().getTransformations()) {
|
| 1727 |
+
+ llvm::errs() << " " << t;
|
| 1728 |
+
+ }
|
| 1729 |
+
+ llvm::errs() << char(10);
|
| 1730 |
+
+ }
|
| 1731 |
+
+ return supported;
|
| 1732 |
+
}
|
| 1733 |
+
|
| 1734 |
+
// Choose only constants that are used in multiple kernel functions
|
| 1735 |
+
@@ -345,6 +369,9 @@ mlir::Value createAvgPoolForInterQuantizedConvert(mlir::OpBuilder& builder, mlir
|
| 1736 |
/// Returns an IE operation for the given constant transformation.
|
| 1737 |
mlir::Value createMatchingIeOperation(mlir::OpBuilder& builder, mlir::Location loc, mlir::Value input,
|
| 1738 |
Const::TransformAttrInterface t) {
|
|
|
|
| 1742 |
return llvm::TypeSwitch<Const::TransformAttrInterface, mlir::Value>(t)
|
| 1743 |
.Case<Const::AddAttr>([&](Const::AddAttr add) {
|
| 1744 |
const auto biasValue = checked_cast<float>(add.getBias().getValueAsDouble());
|
| 1745 |
+
@@ -398,15 +425,94 @@ mlir::Value createMatchingIeOperation(mlir::OpBuilder& builder, mlir::Location l
|
| 1746 |
const auto inElemType = mlir::cast<NDTypeInterface>(input.getType()).getElementType();
|
| 1747 |
const auto outElemType = cast.getElemType();
|
| 1748 |
|
|
|
|
| 1839 |
const auto inElemType = mlir::dyn_cast_or_null<mlir::quant::QuantizedType>(
|
| 1840 |
mlir::cast<NDTypeInterface>(input.getType()).getElementType());
|
| 1841 |
const auto outElemType = mlir::dyn_cast_or_null<mlir::quant::QuantizedType>(convert.getElemType());
|
| 1842 |
+
@@ -480,15 +586,25 @@ mlir::Value createMatchingIeOperation(mlir::OpBuilder& builder, mlir::Location l
|
| 1843 |
+
})
|
| 1844 |
+
.Case<Const::RescaleAttr>([&](Const::RescaleAttr rescale) {
|
| 1845 |
+
auto foldedAttr = rescale.getScale().fold();
|
| 1846 |
+
- const auto scaleValue = foldedAttr.getSplatValue<float>();
|
| 1847 |
+
-
|
| 1848 |
+
const auto scaleLoc = appendLoc(loc, "scale");
|
| 1849 |
+
- SmallVector<int64_t> shapeRank = {1};
|
| 1850 |
+
- auto scaleType = mlir::RankedTensorType::get({shapeRank}, mlir::Float32Type::get(builder.getContext()));
|
| 1851 |
+
auto transform = [&](Const::ContentSetup& setup) -> Const::ContentSetup {
|
| 1852 |
+
return setup.castElemType(mlir::cast<NDTypeInterface>(input.getType()).getElementType());
|
| 1853 |
+
};
|
| 1854 |
+
- auto scale = Const::createConst<float>(builder, scaleLoc, scaleType, {scaleValue}, transform);
|
| 1855 |
+
+ mlir::Value scale;
|
| 1856 |
+
+ if (foldedAttr.isSplat()) {
|
| 1857 |
+
+ const auto scaleValue = foldedAttr.getSplatValue<float>();
|
| 1858 |
+
+ SmallVector<int64_t> shapeRank = {1};
|
| 1859 |
+
+ auto scaleType =
|
| 1860 |
+
+ mlir::RankedTensorType::get({shapeRank}, mlir::Float32Type::get(builder.getContext()));
|
| 1861 |
+
+ scale = Const::createConst<float>(builder, scaleLoc, scaleType, {scaleValue}, transform);
|
| 1862 |
+
+ } else {
|
| 1863 |
+
+ // onw: per-channel scales (2-bit weights): a constant of the scale's own shape, broadcast by the
|
| 1864 |
+
+ // Multiply (numpy rules, as Const::RescaleAttr::transform applies it)
|
| 1865 |
+
+ const auto values = to_small_vector(foldedAttr.getValues<float>());
|
| 1866 |
+
+ const auto shape = to_small_vector(rescale.getScale().getType().getShape().raw());
|
| 1867 |
+
+ auto scaleType = mlir::RankedTensorType::get(shape, mlir::Float32Type::get(builder.getContext()));
|
| 1868 |
+
+ scale = Const::createConst<float>(builder, scaleLoc, scaleType, values, transform);
|
| 1869 |
+
+ }
|
| 1870 |
+
|
| 1871 |
+
return builder.create<IE::MultiplyOp>(loc, input, scale, IE::AutoBroadcastType::NUMPY,
|
| 1872 |
+
/*postOp=*/nullptr, /*clamp=*/nullptr,
|
| 1873 |
+
@@ -1071,6 +1187,330 @@ std::tuple<ArrayRef<Const::TransformAttrInterface>, mlir::Value> ConstOpConverte
|
| 1874 |
+
return {transformations.drop_front(), outputValue};
|
| 1875 |
+
}
|
| 1876 |
+
|
| 1877 |
+
+namespace {
|
| 1878 |
+
+// onw: sub-byte weights (2-bit Bonsai, 4-bit) in init schedules. Their constant chains unpack the codes to f16 only to
|
| 1879 |
+
+// move them around (transpose, subview, reshape) and cast them back to a sub-byte type of the same storage - which the
|
| 1880 |
+
+// NPU 40XX Convert kernels cannot write for 2 bits ("f16 -> ui2 not supported"). When the whole chain only moves the
|
| 1881 |
+
+// values (value-preserving casts and view ops), its result is a box of the original tensor in its own order, reshaped:
|
| 1882 |
+
+// that is a Slice + Reshape on the packed codes plus a reinterpretation of the storage - no unpacking at all.
|
| 1883 |
+
+
|
| 1884 |
+
+mlir::Type subByteStorage(mlir::Type t) {
|
| 1885 |
+
+ for (int k = 0; k < 2; ++k) {
|
| 1886 |
+
+ if (auto q = mlir::dyn_cast<mlir::quant::QuantizedType>(t)) {
|
| 1887 |
+
+ t = q.getStorageType();
|
| 1888 |
+
+ } else if (auto q = mlir::dyn_cast<vpux::type::QuantileType>(t)) {
|
| 1889 |
+
+ t = q.getStorageType();
|
| 1890 |
+
+ }
|
| 1891 |
+
+ }
|
| 1892 |
+
+ return t;
|
| 1893 |
+
+}
|
| 1894 |
+
+
|
| 1895 |
+
+// a cast that keeps the code values of `storage` (unsigned / signed integers of width w): integer widening of the same
|
| 1896 |
+
+// signedness, integer -> float, float -> float of >= 16 bits, float / integer -> a type stored as `storage`, and
|
| 1897 |
+
+// quantized / quantile reinterpretations of the same storage
|
| 1898 |
+
+bool keepsCodes(mlir::Type in, mlir::Type out, mlir::IntegerType storage) {
|
| 1899 |
+
+ const auto inS = subByteStorage(in), outS = subByteStorage(out);
|
| 1900 |
+
+ auto isInt = [](mlir::Type t) {
|
| 1901 |
+
+ return mlir::isa<mlir::IntegerType>(t);
|
| 1902 |
+
+ };
|
| 1903 |
+
+ auto isFloat16Plus = [](mlir::Type t) {
|
| 1904 |
+
+ return mlir::isa<mlir::FloatType>(t) && mlir::cast<mlir::FloatType>(t).getWidth() >= 16;
|
| 1905 |
+
+ };
|
| 1906 |
+
+ if (outS == storage) {
|
| 1907 |
+
+ return inS == storage || isFloat16Plus(inS) ||
|
| 1908 |
+
+ (isInt(inS) && mlir::cast<mlir::IntegerType>(inS).isSigned() == storage.isSigned());
|
| 1909 |
+
+ }
|
| 1910 |
+
+ // the lookup table of NPU 40XX+ (convert_to_palletization_lut_strategy): signed codes become unsigned table
|
| 1911 |
+
+ // indices with the same bits (-7 = 4'b1001 is index 9) - a reinterpretation of storage, as the compiler meant it
|
| 1912 |
+
+ if (inS == storage && isInt(outS) && mlir::cast<mlir::IntegerType>(outS).getWidth() == storage.getWidth() &&
|
| 1913 |
+
+ mlir::isa<mlir::quant::QuantizedType>(out) &&
|
| 1914 |
+
+ mlir::isa<vpux::type::QuantileType>(mlir::cast<mlir::quant::QuantizedType>(out).getStorageType())) {
|
| 1915 |
+
+ return true;
|
| 1916 |
+
+ }
|
| 1917 |
+
+ if (isInt(inS) && isInt(outS)) {
|
| 1918 |
+
+ const auto a = mlir::cast<mlir::IntegerType>(inS), b = mlir::cast<mlir::IntegerType>(outS);
|
| 1919 |
+
+ return b.getWidth() >= a.getWidth() && a.isSigned() == b.isSigned() && a.isSigned() == storage.isSigned();
|
| 1920 |
+
+ }
|
| 1921 |
+
+ if (isInt(inS) && isFloat16Plus(outS)) {
|
| 1922 |
+
+ return true;
|
| 1923 |
+
+ }
|
| 1924 |
+
+ return isFloat16Plus(inS) && isFloat16Plus(outS);
|
| 1925 |
+
+}
|
| 1926 |
+
+
|
| 1927 |
+
+struct View {
|
| 1928 |
+
+ SmallVector<int64_t> shape, strides;
|
| 1929 |
+
+ int64_t offset = 0;
|
| 1930 |
+
+};
|
| 1931 |
+
+
|
| 1932 |
+
+// strides of `newShape` over the same elements as `v` (a reshape of a view), or false when not expressible
|
| 1933 |
+
+bool reshapeView(View& v, ArrayRef<int64_t> newShape) {
|
| 1934 |
+
+ // drop size-1 dims, then match products of old and new dims chunk by chunk
|
| 1935 |
+
+ SmallVector<int64_t> os, ost;
|
| 1936 |
+
+ for (size_t i = 0; i < v.shape.size(); ++i) {
|
| 1937 |
+
+ if (v.shape[i] != 1) {
|
| 1938 |
+
+ os.push_back(v.shape[i]);
|
| 1939 |
+
+ ost.push_back(v.strides[i]);
|
| 1940 |
+
+ }
|
| 1941 |
+
+ }
|
| 1942 |
+
+ SmallVector<int64_t> nst(newShape.size(), 0);
|
| 1943 |
+
+ size_t oi = 0;
|
| 1944 |
+
+ size_t ni = 0;
|
| 1945 |
+
+ while (ni < newShape.size()) {
|
| 1946 |
+
+ if (newShape[ni] == 1) {
|
| 1947 |
+
+ nst[ni] = 1;
|
| 1948 |
+
+ ++ni;
|
| 1949 |
+
+ continue;
|
| 1950 |
+
+ }
|
| 1951 |
+
+ if (oi >= os.size()) {
|
| 1952 |
+
+ return false;
|
| 1953 |
+
+ }
|
| 1954 |
+
+ // a chunk: old dims oi..oj (contiguous in the view) against new dims ni..nj with the same product
|
| 1955 |
+
+ int64_t op = os[oi], np = newShape[ni];
|
| 1956 |
+
+ size_t oj = oi, nj = ni;
|
| 1957 |
+
+ while (op != np) {
|
| 1958 |
+
+ if (op < np) {
|
| 1959 |
+
+ if (++oj >= os.size() || ost[oj - 1] != ost[oj] * os[oj]) {
|
| 1960 |
+
+ return false;
|
| 1961 |
+
+ }
|
| 1962 |
+
+ op *= os[oj];
|
| 1963 |
+
+ } else {
|
| 1964 |
+
+ if (++nj >= newShape.size()) {
|
| 1965 |
+
+ return false;
|
| 1966 |
+
+ }
|
| 1967 |
+
+ np *= newShape[nj];
|
| 1968 |
+
+ }
|
| 1969 |
+
+ }
|
| 1970 |
+
+ for (size_t k = oi; k < oj; ++k) {
|
| 1971 |
+
+ if (ost[k] != ost[k + 1] * os[k + 1]) {
|
| 1972 |
+
+ return false;
|
| 1973 |
+
+ }
|
| 1974 |
+
+ }
|
| 1975 |
+
+ int64_t st = ost[oj];
|
| 1976 |
+
+ for (size_t k = nj + 1; k-- > ni;) {
|
| 1977 |
+
+ nst[k] = st;
|
| 1978 |
+
+ st *= newShape[k];
|
| 1979 |
+
+ }
|
| 1980 |
+
+ oi = oj + 1;
|
| 1981 |
+
+ ni = nj + 1;
|
| 1982 |
+
+ }
|
| 1983 |
+
+ if (oi != os.size()) {
|
| 1984 |
+
+ return false;
|
| 1985 |
+
+ }
|
| 1986 |
+
+ v.shape.assign(newShape.begin(), newShape.end());
|
| 1987 |
+
+ v.strides = nst;
|
| 1988 |
+
+ return true;
|
| 1989 |
+
+}
|
| 1990 |
+
+
|
| 1991 |
+
+// The chain `ts` applied to `input` (sub-byte codes) as Slice + Reshape + QuantizeCast, or nullptr.
|
| 1992 |
+
+// [onw] Group rows: a view that takes k consecutive groups of a weight [N, G, K] as [k, N, K] (the merged group
|
| 1993 |
+
+// matmuls of a one-token graph: groups and rows swapped) is no box of the weight; it is built as a Concat of the k
|
| 1994 |
+
+// group slices [N, 1, K] (byte moves of the packed codes), then sliced / reshaped like a box of that [k, N, K] stack.
|
| 1995 |
+
+mlir::Value foldSubByteView(mlir::OpBuilder& builder, mlir::Location loc, mlir::Value input,
|
| 1996 |
+
+ ArrayRef<Const::TransformAttrInterface> ts) {
|
| 1997 |
+
+ if (std::getenv("ONW_WS_VIEWFOLD") != nullptr && std::string(std::getenv("ONW_WS_VIEWFOLD")) == "0") {
|
| 1998 |
+
+ return nullptr;
|
| 1999 |
+
+ }
|
| 2000 |
+
+ auto inType = mlir::cast<NDTypeInterface>(input.getType());
|
| 2001 |
+
+ auto storage = mlir::dyn_cast<mlir::IntegerType>(subByteStorage(inType.getElementType()));
|
| 2002 |
+
+ if (storage == nullptr || storage.getWidth() >= 8 || ts.empty() || !inType.getDimsOrder().isIdentity()) {
|
| 2003 |
+
+ return nullptr;
|
| 2004 |
+
+ }
|
| 2005 |
+
+ const auto origShape = to_small_vector(inType.getShape().raw());
|
| 2006 |
+
+ SmallVector<int64_t> srcShape = origShape; // what the view addresses: the weight, or the group stack
|
| 2007 |
+
+ std::optional<std::pair<int64_t, int64_t>> groupRows; // (first group, groups) once rebased onto the stack
|
| 2008 |
+
+ View v;
|
| 2009 |
+
+ v.shape = origShape;
|
| 2010 |
+
+ v.strides.assign(origShape.size(), 1);
|
| 2011 |
+
+ for (size_t i = origShape.size() - 1; i-- > 0;) {
|
| 2012 |
+
+ v.strides[i] = v.strides[i + 1] * origShape[i + 1];
|
| 2013 |
+
+ }
|
| 2014 |
+
+ // the view as k groups x N rows x K of a [N, G, K] weight (all rows, all of K) -> the same view on the stack
|
| 2015 |
+
+ const auto rebase = [&]() -> bool {
|
| 2016 |
+
+ if (groupRows.has_value() || origShape.size() != 3 || std::getenv("ONW_WS_NO_GROUP_ROWS") != nullptr) {
|
| 2017 |
+
+ return false;
|
| 2018 |
+
+ }
|
| 2019 |
+
+ const int64_t N = origShape[0], G = origShape[1], K = origShape[2];
|
| 2020 |
+
+ SmallVector<size_t> dims;
|
| 2021 |
+
+ for (size_t i = 0; i < v.shape.size(); ++i) {
|
| 2022 |
+
+ if (v.shape[i] != 1) {
|
| 2023 |
+
+ dims.push_back(i);
|
| 2024 |
+
+ }
|
| 2025 |
+
+ }
|
| 2026 |
+
+ if (dims.size() != 3 || v.strides[dims[0]] != K || v.shape[dims[1]] != N || v.strides[dims[1]] != G * K ||
|
| 2027 |
+
+ v.shape[dims[2]] != K || v.strides[dims[2]] != 1 || v.offset % K != 0) {
|
| 2028 |
+
+ return false;
|
| 2029 |
+
+ }
|
| 2030 |
+
+ const int64_t g0 = v.offset / K, k = v.shape[dims[0]];
|
| 2031 |
+
+ if (g0 < 0 || g0 + k > G || k < 2) {
|
| 2032 |
+
+ return false;
|
| 2033 |
+
+ }
|
| 2034 |
+
+ groupRows = std::make_pair(g0, k);
|
| 2035 |
+
+ srcShape = {k, N, K};
|
| 2036 |
+
+ v.strides[dims[0]] = N * K;
|
| 2037 |
+
+ v.strides[dims[1]] = K;
|
| 2038 |
+
+ v.strides[dims[2]] = 1;
|
| 2039 |
+
+ v.offset = 0;
|
| 2040 |
+
+ return true;
|
| 2041 |
+
+ };
|
| 2042 |
+
+ NDTypeInterface cur = inType;
|
| 2043 |
+
+ for (auto t : ts) {
|
| 2044 |
+
+ const auto next = t.inferOutputType(cur);
|
| 2045 |
+
+ const auto reshapeTo = [&](ArrayRef<int64_t> shape) {
|
| 2046 |
+
+ View keep = v;
|
| 2047 |
+
+ if (reshapeView(v, shape)) {
|
| 2048 |
+
+ return true;
|
| 2049 |
+
+ }
|
| 2050 |
+
+ v = keep;
|
| 2051 |
+
+ return rebase() && reshapeView(v, shape);
|
| 2052 |
+
+ };
|
| 2053 |
+
+ const bool ok = llvm::TypeSwitch<Const::TransformAttrInterface, bool>(t)
|
| 2054 |
+
+ .Case<Const::CastElemTypeAttr, Const::ConvertElemTypeAttr>([&](auto) {
|
| 2055 |
+
+ return keepsCodes(cur.getElementType(), next.getElementType(), storage);
|
| 2056 |
+
+ })
|
| 2057 |
+
+ .Case<Const::TransposeAttr>([&](Const::TransposeAttr tr) {
|
| 2058 |
+
+ const auto map = tr.getOrder().getValue();
|
| 2059 |
+
+ View w;
|
| 2060 |
+
+ for (unsigned i = 0; i < map.getNumResults(); ++i) {
|
| 2061 |
+
+ const auto d = map.getDimPosition(i);
|
| 2062 |
+
+ w.shape.push_back(v.shape[d]);
|
| 2063 |
+
+ w.strides.push_back(v.strides[d]);
|
| 2064 |
+
+ }
|
| 2065 |
+
+ w.offset = v.offset;
|
| 2066 |
+
+ v = w;
|
| 2067 |
+
+ return true;
|
| 2068 |
+
+ })
|
| 2069 |
+
+ .Case<Const::SubViewAttr>([&](Const::SubViewAttr sv) {
|
| 2070 |
+
+ const auto off = parseIntArrayAttr<int64_t>(sv.getOffset());
|
| 2071 |
+
+ const auto shp = parseIntArrayAttr<int64_t>(sv.getShape());
|
| 2072 |
+
+ for (size_t i = 0; i < off.size(); ++i) {
|
| 2073 |
+
+ v.offset += off[i] * v.strides[i];
|
| 2074 |
+
+ }
|
| 2075 |
+
+ v.shape.assign(shp.begin(), shp.end());
|
| 2076 |
+
+ return true;
|
| 2077 |
+
+ })
|
| 2078 |
+
+ .Case<Const::ReshapeAttr>([&](Const::ReshapeAttr r) {
|
| 2079 |
+
+ return reshapeTo(parseIntArrayAttr<int64_t>(r.getShape()));
|
| 2080 |
+
+ })
|
| 2081 |
+
+ .Case<Const::AffineReshapeAttr>([&](Const::AffineReshapeAttr r) {
|
| 2082 |
+
+ return reshapeTo(parseIntArrayAttr<int64_t>(r.getShapeValue()));
|
| 2083 |
+
+ })
|
| 2084 |
+
+ .Default([](Const::TransformAttrInterface) {
|
| 2085 |
+
+ return false;
|
| 2086 |
+
+ });
|
| 2087 |
+
+ if (!ok || !next.getDimsOrder().isIdentity()) {
|
| 2088 |
+
+ return nullptr;
|
| 2089 |
+
+ }
|
| 2090 |
+
+ cur = next;
|
| 2091 |
+
+ }
|
| 2092 |
+
+ {
|
| 2093 |
+
+ auto outStorage = mlir::dyn_cast<mlir::IntegerType>(subByteStorage(cur.getElementType()));
|
| 2094 |
+
+ if (outStorage == nullptr || outStorage.getWidth() != storage.getWidth()) {
|
| 2095 |
+
+ return nullptr;
|
| 2096 |
+
+ }
|
| 2097 |
+
+ }
|
| 2098 |
+
+ // the result as a box of the source (the weight or the group stack) in its own row-major order: walk the non-1
|
| 2099 |
+
+ // result dims from the innermost out, each covering one source dim (or several whole inner ones)
|
| 2100 |
+
+ SmallVector<int64_t> boxOff, boxSize;
|
| 2101 |
+
+ const auto findBox = [&]() -> bool {
|
| 2102 |
+
+ boxOff.assign(srcShape.size(), 0);
|
| 2103 |
+
+ boxSize.assign(srcShape.size(), 1);
|
| 2104 |
+
+ int64_t rem = v.offset;
|
| 2105 |
+
+ SmallVector<int64_t> ost(srcShape.size(), 1);
|
| 2106 |
+
+ for (size_t i = srcShape.size() - 1; i-- > 0;) {
|
| 2107 |
+
+ ost[i] = ost[i + 1] * srcShape[i + 1];
|
| 2108 |
+
+ }
|
| 2109 |
+
+ for (size_t i = 0; i < srcShape.size(); ++i) {
|
| 2110 |
+
+ boxOff[i] = rem / ost[i];
|
| 2111 |
+
+ rem %= ost[i];
|
| 2112 |
+
+ }
|
| 2113 |
+
+ int d = static_cast<int>(srcShape.size()) - 1;
|
| 2114 |
+
+ for (size_t k = v.shape.size(); k-- > 0;) {
|
| 2115 |
+
+ int64_t n = v.shape[k];
|
| 2116 |
+
+ if (n == 1) {
|
| 2117 |
+
+ continue;
|
| 2118 |
+
+ }
|
| 2119 |
+
+ const int64_t s = v.strides[k];
|
| 2120 |
+
+ while (d >= 0 && (ost[d] != s || srcShape[d] == 1)) {
|
| 2121 |
+
+ if (ost[d] < s) {
|
| 2122 |
+
+ --d; // a source dim skipped: the box takes one element of it (its offset)
|
| 2123 |
+
+ continue;
|
| 2124 |
+
+ }
|
| 2125 |
+
+ return false; // a stride that is no source dim's: the values are reordered
|
| 2126 |
+
+ }
|
| 2127 |
+
+ if (d < 0) {
|
| 2128 |
+
+ return false;
|
| 2129 |
+
+ }
|
| 2130 |
+
+ // n elements from dim d up (the dims above d must then be taken whole)
|
| 2131 |
+
+ while (n > 1) {
|
| 2132 |
+
+ if (d < 0) {
|
| 2133 |
+
+ return false;
|
| 2134 |
+
+ }
|
| 2135 |
+
+ if (n <= srcShape[d] - boxOff[d] && (n == 1 || boxSize[d] == 1)) {
|
| 2136 |
+
+ boxSize[d] = n;
|
| 2137 |
+
+ n = 1;
|
| 2138 |
+
+ } else if (boxOff[d] == 0 && n % srcShape[d] == 0 && boxSize[d] == 1) {
|
| 2139 |
+
+ boxSize[d] = srcShape[d];
|
| 2140 |
+
+ n /= srcShape[d];
|
| 2141 |
+
+ } else {
|
| 2142 |
+
+ return false;
|
| 2143 |
+
+ }
|
| 2144 |
+
+ --d;
|
| 2145 |
+
+ }
|
| 2146 |
+
+ }
|
| 2147 |
+
+ return true;
|
| 2148 |
+
+ };
|
| 2149 |
+
+ if (!findBox()) {
|
| 2150 |
+
+ if (!rebase() || !findBox()) {
|
| 2151 |
+
+ return nullptr;
|
| 2152 |
+
+ }
|
| 2153 |
+
+ }
|
| 2154 |
+
+ // the box in row-major order must be exactly the result's element order: the result's dims walked above came in
|
| 2155 |
+
+ // decreasing source dims, so it is; check the element count and the byte alignment of the innermost box
|
| 2156 |
+
+ int64_t total = 1, boxTotal = 1;
|
| 2157 |
+
+ for (auto n : v.shape) {
|
| 2158 |
+
+ total *= n;
|
| 2159 |
+
+ }
|
| 2160 |
+
+ for (auto n : boxSize) {
|
| 2161 |
+
+ boxTotal *= n;
|
| 2162 |
+
+ }
|
| 2163 |
+
+ const auto bits = storage.getWidth();
|
| 2164 |
+
+ const auto inner = srcShape.size() - 1;
|
| 2165 |
+
+ if (total != boxTotal || (boxOff[inner] * bits) % 8 != 0 || (boxSize[inner] * bits) % 8 != 0) {
|
| 2166 |
+
+ return nullptr;
|
| 2167 |
+
+ }
|
| 2168 |
+
+ auto ctx = builder.getContext();
|
| 2169 |
+
+ mlir::Value x = input;
|
| 2170 |
+
+ if (inType.getElementType() != storage) { // (quantized input: slice the raw codes, not the axes)
|
| 2171 |
+
+ x = builder.create<IE::QuantizeCastOp>(appendLoc(loc, "onw_view_raw"), x, storage);
|
| 2172 |
+
+ }
|
| 2173 |
+
+ if (groupRows.has_value()) {
|
| 2174 |
+
+ const auto [g0, k] = groupRows.value();
|
| 2175 |
+
+ SmallVector<mlir::Value> groups;
|
| 2176 |
+
+ for (int64_t g = g0; g < g0 + k; ++g) {
|
| 2177 |
+
+ auto sl = builder.create<IE::SliceOp>(appendLoc(loc, "onw_group_{0}", g), x,
|
| 2178 |
+
+ getIntArrayAttr(ctx, SmallVector<int64_t>{0, g, 0}),
|
| 2179 |
+
+ getIntArrayAttr(ctx, SmallVector<int64_t>{origShape[0], 1, origShape[2]}));
|
| 2180 |
+
+ groups.push_back(builder.create<IE::ReshapeOp>(appendLoc(loc, "onw_group_row_{0}", g), sl,
|
| 2181 |
+
+ getIntArrayAttr(ctx, SmallVector<int64_t>{1, origShape[0],
|
| 2182 |
+
+ origShape[2]})));
|
| 2183 |
+
+ }
|
| 2184 |
+
+ x = builder.create<IE::ConcatOp>(appendLoc(loc, "onw_group_rows"), groups, Dim(0));
|
| 2185 |
+
+ }
|
| 2186 |
+
+ if (boxSize != srcShape) {
|
| 2187 |
+
+ x = builder.create<IE::SliceOp>(appendLoc(loc, "onw_view_slice"), x, getIntArrayAttr(ctx, boxOff),
|
| 2188 |
+
+ getIntArrayAttr(ctx, boxSize));
|
| 2189 |
+
+ }
|
| 2190 |
+
+ const auto outShape = to_small_vector(cur.getShape().raw());
|
| 2191 |
+
+ if (to_small_vector(mlir::cast<NDTypeInterface>(x.getType()).getShape().raw()) != outShape) {
|
| 2192 |
+
+ x = builder.create<IE::ReshapeOp>(appendLoc(loc, "onw_view_reshape"), x, getIntArrayAttr(ctx, outShape));
|
| 2193 |
+
+ }
|
| 2194 |
+
+ if (mlir::cast<NDTypeInterface>(x.getType()).getElementType() != cur.getElementType()) {
|
| 2195 |
+
+ x = builder.create<IE::QuantizeCastOp>(appendLoc(loc, "onw_view_cast"), x, cur.getElementType());
|
| 2196 |
+
+ }
|
| 2197 |
+
+ return x;
|
| 2198 |
+
+}
|
| 2199 |
+
+} // namespace
|
| 2200 |
+
+
|
| 2201 |
+
mlir::Value ConstOpConverter::convertToIrForm(mlir::Location loc, const VPU::TransformationsSplit::Projection& split,
|
| 2202 |
+
mlir::BlockArgument argValue, const IoBoundaryAdapter& ioAdaptor,
|
| 2203 |
+
WeightsSeparationSchedule scheduleKind) {
|
| 2204 |
+
@@ -1091,6 +1531,29 @@ mlir::Value ConstOpConverter::convertToIrForm(mlir::Location loc, const VPU::Tra
|
| 2205 |
+
_log.trace("Creating matching operations for '{0}'", contentAttr);
|
| 2206 |
+
_log.trace(" These transformations are converted into IR '{0}'", transformations);
|
| 2207 |
+
|
| 2208 |
+
+ if (scheduleKind == WeightsSeparationSchedule::Init && !transformations.empty()) {
|
| 2209 |
+
+ std::vector<const void*> viewKey{value.getAsOpaquePointer()};
|
| 2210 |
+
+ for (auto t : transformations) {
|
| 2211 |
+
+ viewKey.push_back(t.getAsOpaquePointer());
|
| 2212 |
+
+ }
|
| 2213 |
+
+ mlir::Value folded;
|
| 2214 |
+
+ if (auto it = _onwViewCache.find(viewKey); it != _onwViewCache.end()) {
|
| 2215 |
+
+ folded = it->second;
|
| 2216 |
+
+ } else {
|
| 2217 |
+
+ folded = foldSubByteView(_opBuilder, appendLoc(loc, "onw_view"), value, transformations);
|
| 2218 |
+
+ if (folded) {
|
| 2219 |
+
+ _onwViewCache.emplace(std::move(viewKey), folded);
|
| 2220 |
+
+ }
|
| 2221 |
+
+ }
|
| 2222 |
+
+ if (folded) {
|
| 2223 |
+
+ if (std::getenv("ONW_DEBUG_WS") != nullptr) {
|
| 2224 |
+
+ llvm::errs() << "ONW ws viewfold: " << value.getType() << " -> " << folded.getType() << char(10);
|
| 2225 |
+
+ }
|
| 2226 |
+
+ value = folded;
|
| 2227 |
+
+ transformations = {};
|
| 2228 |
+
+ }
|
| 2229 |
+
+ }
|
| 2230 |
+
+
|
| 2231 |
+
// convert transformation chain to operations
|
| 2232 |
+
size_t count = 0;
|
| 2233 |
+
while (!transformations.empty()) {
|
| 2234 |
+
@@ -1152,8 +1615,119 @@ void removeRepeatedConstantsForHostPipeline(std::vector<Const::DeclareOp>& const
|
| 2235 |
+
}
|
| 2236 |
+
} // namespace
|
| 2237 |
+
|
| 2238 |
+
+namespace {
|
| 2239 |
+
+// onw: MergeFullyConnected joins the unrolled group matmuls of a 2-bit weight [N, G, K] (one-token graphs): the weight
|
| 2240 |
+
+// of k groups becomes a Concat of k constants, each the same chain on the same OV constant ending with a one-group
|
| 2241 |
+
+// SubView, on a dummy base - not an OV constant, so it stayed in the blob (a second copy of the codes) and the merged
|
| 2242 |
+
+// matmul could not go weightless. When the parts are consecutive groups of one constant the Concat is that chain with
|
| 2243 |
+
+// a k-group SubView: rewritten so, the weight is an OV constant again (its init builds it, see foldSubByteView).
|
| 2244 |
+
+// ONW_WS_NO_CODE_CONCAT=1: as before.
|
| 2245 |
+
+void onwCanonicalizeCodeConcats(mlir::ModuleOp moduleOp) {
|
| 2246 |
+
+ if (!moduleOp->hasAttr("VPU.OnwWeightless") || std::getenv("ONW_WS_NO_CODE_CONCAT") != nullptr) {
|
| 2247 |
+
+ return;
|
| 2248 |
+
+ }
|
| 2249 |
+
+ SmallVector<Const::DeclareOp> ops;
|
| 2250 |
+
+ moduleOp.walk([&](Const::DeclareOp op) {
|
| 2251 |
+
+ ops.push_back(op);
|
| 2252 |
+
+ });
|
| 2253 |
+
+ size_t rewritten = 0;
|
| 2254 |
+
+ for (auto op : ops) {
|
| 2255 |
+
+ const auto content = op.getContentAttr();
|
| 2256 |
+
+ const auto ts = content.getTransformations();
|
| 2257 |
+
+ if (ts.empty() || !Const::isDummyBaseContent(content.getBaseContent())) {
|
| 2258 |
+
+ continue;
|
| 2259 |
+
+ }
|
| 2260 |
+
+ auto concat = mlir::dyn_cast<Const::ConcatAttr>(ts.front());
|
| 2261 |
+
+ if (concat == nullptr || concat.getAxis().getInt() != 0) {
|
| 2262 |
+
+ continue;
|
| 2263 |
+
+ }
|
| 2264 |
+
+ const auto constants = concat.getConstants().getValue();
|
| 2265 |
+
+ const auto offsets = parseIntArrayOfArrayAttr<int64_t>(concat.getStaticOffsets());
|
| 2266 |
+
+ if (constants.size() < 2 || offsets.size() != constants.size()) {
|
| 2267 |
+
+ continue;
|
| 2268 |
+
+ }
|
| 2269 |
+
+ const auto first = constants[0];
|
| 2270 |
+
+ const auto base = first.getBaseContent();
|
| 2271 |
+
+ const auto baseType = mlir::cast<NDTypeInterface>(base.getType());
|
| 2272 |
+
+ auto storage = mlir::dyn_cast<mlir::IntegerType>(baseType.getElementType());
|
| 2273 |
+
+ if (!getResourceName(base).starts_with(Const::IMPORTED_WEIGHT_PREFIX) || storage == nullptr || storage.getWidth() >= 8 ||
|
| 2274 |
+
+ baseType.getRank() != 3) {
|
| 2275 |
+
+ continue;
|
| 2276 |
+
+ }
|
| 2277 |
+
+ const auto firstTs = first.getTransformations();
|
| 2278 |
+
+ if (firstTs.empty() || !mlir::isa<Const::SubViewAttr>(firstTs.back())) {
|
| 2279 |
+
+ continue;
|
| 2280 |
+
+ }
|
| 2281 |
+
+ const auto prefix = firstTs.drop_back(1);
|
| 2282 |
+
+ // the SubView's input: [G', ...] with the groups on dim 0 (the parts' chains transpose them there)
|
| 2283 |
+
+ const auto prefixType = Const::inferFinalType(baseType, prefix);
|
| 2284 |
+
+ bool ok = true;
|
| 2285 |
+
+ int64_t g0 = -1;
|
| 2286 |
+
+ SmallVector<int64_t> partShape;
|
| 2287 |
+
+ for (size_t i = 0; i < constants.size() && ok; ++i) {
|
| 2288 |
+
+ const auto c = constants[i];
|
| 2289 |
+
+ const auto cts = c.getTransformations();
|
| 2290 |
+
+ ok = c.getBaseContent() == base && cts.size() == firstTs.size();
|
| 2291 |
+
+ for (size_t j = 0; ok && j + 1 < cts.size(); ++j) {
|
| 2292 |
+
+ ok = cts[j] == firstTs[j];
|
| 2293 |
+
+ }
|
| 2294 |
+
+ if (!ok) {
|
| 2295 |
+
+ break;
|
| 2296 |
+
+ }
|
| 2297 |
+
+ auto sv = mlir::dyn_cast<Const::SubViewAttr>(cts.back());
|
| 2298 |
+
+ ok = sv != nullptr;
|
| 2299 |
+
+ if (!ok) {
|
| 2300 |
+
+ break;
|
| 2301 |
+
+ }
|
| 2302 |
+
+ const auto off = parseIntArrayAttr<int64_t>(sv.getOffset());
|
| 2303 |
+
+ const auto shp = parseIntArrayAttr<int64_t>(sv.getShape());
|
| 2304 |
+
+ const auto full = to_small_vector(prefixType.getShape().raw());
|
| 2305 |
+
+ ok = off.size() == full.size() && shp.size() == full.size() && shp[0] == 1;
|
| 2306 |
+
+ for (size_t d = 1; ok && d < full.size(); ++d) {
|
| 2307 |
+
+ ok = off[d] == 0 && shp[d] == full[d];
|
| 2308 |
+
+ }
|
| 2309 |
+
+ if (!ok) {
|
| 2310 |
+
+ break;
|
| 2311 |
+
+ }
|
| 2312 |
+
+ if (i == 0) {
|
| 2313 |
+
+ g0 = off[0];
|
| 2314 |
+
+ partShape = shp;
|
| 2315 |
+
+ }
|
| 2316 |
+
+ ok = off[0] == g0 + static_cast<int64_t>(i) && offsets[i].size() == shp.size() &&
|
| 2317 |
+
+ offsets[i][0] == static_cast<int64_t>(i);
|
| 2318 |
+
+ for (size_t d = 1; ok && d < offsets[i].size(); ++d) {
|
| 2319 |
+
+ ok = offsets[i][d] == 0;
|
| 2320 |
+
+ }
|
| 2321 |
+
+ }
|
| 2322 |
+
+ if (!ok || g0 < 0) {
|
| 2323 |
+
+ continue;
|
| 2324 |
+
+ }
|
| 2325 |
+
+ SmallVector<int64_t> newOff(partShape.size(), 0), newShape(partShape.begin(), partShape.end());
|
| 2326 |
+
+ newOff[0] = g0;
|
| 2327 |
+
+ newShape[0] = static_cast<int64_t>(constants.size());
|
| 2328 |
+
+ SmallVector<Const::TransformAttrInterface> newTs(prefix.begin(), prefix.end());
|
| 2329 |
+
+ newTs.push_back(Const::SubViewAttr::get(getIntArrayAttr(op.getContext(), newOff),
|
| 2330 |
+
+ getIntArrayAttr(op.getContext(), newShape)));
|
| 2331 |
+
+ newTs.append(ts.begin() + 1, ts.end());
|
| 2332 |
+
+ const auto newContent = Const::ContentAttr::get(base, newTs);
|
| 2333 |
+
+ if (newContent.getType() != op.getType()) {
|
| 2334 |
+
+ continue;
|
| 2335 |
+
+ }
|
| 2336 |
+
+ mlir::OpBuilder builder(op);
|
| 2337 |
+
+ auto newOp = builder.create<Const::DeclareOp>(op.getLoc(), op.getType(), newContent);
|
| 2338 |
+
+ op.replaceAllUsesWith(newOp.getOutput());
|
| 2339 |
+
+ op.erase();
|
| 2340 |
+
+ ++rewritten;
|
| 2341 |
+
+ }
|
| 2342 |
+
+ if (rewritten != 0 && std::getenv("ONW_DEBUG_WS") != nullptr) {
|
| 2343 |
+
+ llvm::errs() << "ONW ws code concats rewritten: " << rewritten << char(10);
|
| 2344 |
+
+ }
|
| 2345 |
+
+}
|
| 2346 |
+
+} // namespace
|
| 2347 |
+
+
|
| 2348 |
+
WeightsSeparationInfo::WeightsSeparationInfo(mlir::ModuleOp moduleOp) {
|
| 2349 |
+
auto log = Logger::global().nest("weights-separation-info", 0);
|
| 2350 |
+
+ onwCanonicalizeCodeConcats(moduleOp);
|
| 2351 |
+
auto mainFuncOp = net::getMainFunc(moduleOp);
|
| 2352 |
+
auto tree = VPU::getOutliningRepresentation(mainFuncOp);
|
| 2353 |
+
|
| 2354 |
diff --git a/src/vpux_compiler/src/dialect/VPUIP/transforms/passes/convert_async_ops_to_tasks.cpp b/src/vpux_compiler/src/dialect/VPUIP/transforms/passes/convert_async_ops_to_tasks.cpp
|
| 2355 |
index f637bad..6c3db25 100644
|
| 2356 |
--- a/src/vpux_compiler/src/dialect/VPUIP/transforms/passes/convert_async_ops_to_tasks.cpp
|
|
|
|
| 2447 |
}
|
| 2448 |
|
| 2449 |
diff --git a/src/vpux_compiler/src/dialect/const/attributes/content.cpp b/src/vpux_compiler/src/dialect/const/attributes/content.cpp
|
| 2450 |
+
index 3f4f862..69d23e8 100644
|
| 2451 |
--- a/src/vpux_compiler/src/dialect/const/attributes/content.cpp
|
| 2452 |
+++ b/src/vpux_compiler/src/dialect/const/attributes/content.cpp
|
| 2453 |
@@ -7,6 +7,14 @@
|
|
|
|
| 2465 |
#include "vpux/compiler/dialect/const/dialect.hpp"
|
| 2466 |
#include "vpux/compiler/dialect/const/utils/const_logger.hpp"
|
| 2467 |
#include "vpux/compiler/utils/stable_hash.hpp"
|
| 2468 |
+
@@ -468,11 +476,126 @@ mlir::DenseElementsAttr Const::detail::createConstContentWithConversion(mlir::Sh
|
| 2469 |
// ContentAttr::fold
|
| 2470 |
//
|
| 2471 |
|
|
|
|
| 2502 |
- for (const auto& attr : getTransformations()) {
|
| 2503 |
- res = attr.transform(res);
|
| 2504 |
+ const auto transformations = getTransformations();
|
| 2505 |
+
+ // the last SubView that follows a Concat or a permutation: the prefix before it is memoized. [onw] Weightless
|
| 2506 |
+
+ // folding keeps SubViews last (ws::getWsOptimizations: moveSubViewAfter, so the init schedule transforms a weight
|
| 2507 |
+
+ // once), so every group slice of a 2-bit weight [N, G, 128] (and of its scales) transposed the whole tensor again
|
| 2508 |
+
+ // when folded on the host - SplitFakeQuant and the ELF export of 2-bit Bonsai parts (~10 s of a 16 s compile).
|
| 2509 |
+ int64_t cut = -1;
|
| 2510 |
+ bool sawConcat = false;
|
| 2511 |
+
+ static const bool permuteMemo = std::getenv("ONW_NO_PERMUTE_FOLD_MEMO") == nullptr;
|
| 2512 |
+ for (size_t i = 0; i < transformations.size(); ++i) {
|
| 2513 |
+
+ if (mlir::isa<Const::ConcatAttr>(transformations[i]) ||
|
| 2514 |
+
+ (permuteMemo && mlir::isa<Const::TransposeAttr, Const::MemPermuteAttr>(transformations[i]))) {
|
| 2515 |
+ sawConcat = true;
|
| 2516 |
+ } else if (sawConcat && mlir::isa<Const::SubViewAttr>(transformations[i])) {
|
| 2517 |
+ cut = static_cast<int64_t>(i);
|
| 2518 |
+ }
|
| 2519 |
+ }
|
| 2520 |
+ static std::atomic<int64_t> nHit{0}, nMiss{0}, nPlain{0};
|
| 2521 |
+
+ static const bool noFoldMemo = std::getenv("ONW_NO_FOLD_MEMO") != nullptr;
|
| 2522 |
+
+ if (cut < 0 || noFoldMemo) {
|
| 2523 |
+ ++nPlain;
|
| 2524 |
+ auto res = wrapBaseContent(baseContent);
|
| 2525 |
+ for (const auto& attr : transformations) {
|
|
|
|
| 2563 |
+ for (size_t i = static_cast<size_t>(cut) + 1; i < transformations.size(); ++i) {
|
| 2564 |
+ res = transformations[i].transform(res);
|
| 2565 |
+ }
|
| 2566 |
+
+ // a result that still points into the memo's buffer (a SubView / reshape that moved its input instead of
|
| 2567 |
+
+ // copying) must own its data: another thread can evict the entry and free the buffer once `held` is gone
|
| 2568 |
+
+ // (multi-threaded compiles of 2-bit Bonsai 9B parts crashed with an access violation, now and then)
|
| 2569 |
+
+ return Const::Content::copyUnownedBuffer(std::move(res));
|
| 2570 |
+ }
|
| 2571 |
+ if (!hit) {
|
| 2572 |
+ ++nMiss;
|
|
|
|
| 2595 |
}
|
| 2596 |
return res;
|
| 2597 |
}
|
| 2598 |
+
@@ -747,8 +870,18 @@ llvm::hash_code StableHashStorage<QuantizeAttrStorage>::calculateStableHash() co
|
| 2599 |
+
template <>
|
| 2600 |
+
llvm::hash_code StableHashStorage<RescaleAttrStorage>::calculateStableHash() const {
|
| 2601 |
+
if (!this->scale.isSplat()) {
|
| 2602 |
+
- // Note: non-splat rescale is currently not supported
|
| 2603 |
+
- return {};
|
| 2604 |
+
+ // onw: non-splat rescale (per-channel scales, e.g. 2-bit weights) - its shape and values, so that the weights
|
| 2605 |
+
+ // separation can name the transformation chain (upstream: "not supported", QueryWSInfo threw)
|
| 2606 |
+
+ const auto content = this->scale.fold();
|
| 2607 |
+
+ SmallVector<uint32_t> bits;
|
| 2608 |
+
+ for (const auto v : content.getValues<float>()) {
|
| 2609 |
+
+ uint32_t b;
|
| 2610 |
+
+ std::memcpy(&b, &v, sizeof(b));
|
| 2611 |
+
+ bits.push_back(b);
|
| 2612 |
+
+ }
|
| 2613 |
+
+ const auto shape = this->scale.getType().getShape().raw();
|
| 2614 |
+
+ return llvm::hash_combine(RescaleAttr::getMnemonic(), llvm::hash_combine_range(shape.begin(), shape.end()),
|
| 2615 |
+
+ llvm::hash_combine_range(bits.begin(), bits.end()));
|
| 2616 |
+
}
|
| 2617 |
+
|
| 2618 |
+
const auto scale = this->scale.fold().getSplatValue<double>();
|
| 2619 |
+
@@ -868,7 +1001,6 @@ llvm::hash_code StableHashStorage<CumSumAttrStorage>::calculateStableHash() cons
|
| 2620 |
+
} // namespace details
|
| 2621 |
+
|
| 2622 |
+
llvm::hash_code RescaleAttr::getStableHashValue() const {
|
| 2623 |
+
- VPUX_THROW_UNLESS(getScale().isSplat(), "RescaleAttr scale must be splat");
|
| 2624 |
+
return static_cast<details::StableHashStorage<details::RescaleAttrStorage>*>(getImpl())->stableHash;
|
| 2625 |
+
}
|
| 2626 |
+
|
| 2627 |
+
diff --git a/src/vpux_compiler/src/pipelines/developer_config.cpp b/src/vpux_compiler/src/pipelines/developer_config.cpp
|
| 2628 |
+
index 175c65d..5bde0f7 100644
|
| 2629 |
+
--- a/src/vpux_compiler/src/pipelines/developer_config.cpp
|
| 2630 |
+
+++ b/src/vpux_compiler/src/pipelines/developer_config.cpp
|
| 2631 |
+
@@ -19,6 +19,7 @@
|
| 2632 |
+
#include "vpux/utils/core/developer_path_utils.hpp"
|
| 2633 |
+
#endif
|
| 2634 |
+
|
| 2635 |
+
+#include <cstdlib>
|
| 2636 |
+
#include <mlir/Pass/Pass.h>
|
| 2637 |
+
#include <mlir/Support/Timing.h>
|
| 2638 |
+
|
| 2639 |
+
@@ -30,6 +31,18 @@
|
| 2640 |
+
namespace vpux {
|
| 2641 |
+
|
| 2642 |
+
DeveloperConfig::DeveloperConfig(Logger log, mlir::MLIRContext* ctx, const vpux::OV::Config& config): _log(log) {
|
| 2643 |
+
+#if !defined(VPUX_DEVELOPER_BUILD) && defined(NDEBUG)
|
| 2644 |
+
+ // onw: IR dumps in release builds too (ONW_IR_PRINTING_FILTER / _LOCATION / _ORDER)
|
| 2645 |
+
+ if (const char* v = std::getenv("ONW_IR_PRINTING_FILTER")) {
|
| 2646 |
+
+ _irPrintingFilter = v;
|
| 2647 |
+
+ }
|
| 2648 |
+
+ if (const char* v = std::getenv("ONW_IR_PRINTING_LOCATION")) {
|
| 2649 |
+
+ _irPrintingLocation = v;
|
| 2650 |
+
+ }
|
| 2651 |
+
+ if (const char* v = std::getenv("ONW_IR_PRINTING_ORDER")) {
|
| 2652 |
+
+ _irPrintingOrderStr = v;
|
| 2653 |
+
+ }
|
| 2654 |
+
+#endif
|
| 2655 |
+
#if defined(VPUX_DEVELOPER_BUILD) || !defined(NDEBUG)
|
| 2656 |
+
parseEnv("IE_NPU_CRASH_REPRODUCER_FILE", _crashReproducerFile);
|
| 2657 |
+
parseEnv("IE_NPU_GEN_LOCAL_REPRODUCER", _localReproducer);
|
| 2658 |
+
@@ -218,8 +231,38 @@ private:
|
| 2659 |
+
Logger _log;
|
| 2660 |
+
};
|
| 2661 |
+
|
| 2662 |
+
+// onw: ONW_FIND_NULLOP=1 - after every pass, report the first op that has a null operand (debugging passes that
|
| 2663 |
+
+// leave half-built ops behind)
|
| 2664 |
+
+class OnwNullOperandCheck final : public mlir::PassInstrumentation {
|
| 2665 |
+
+public:
|
| 2666 |
+
+ void runAfterPass(mlir::Pass* pass, mlir::Operation* op) final {
|
| 2667 |
+
+ if (_reported) {
|
| 2668 |
+
+ return;
|
| 2669 |
+
+ }
|
| 2670 |
+
+ op->walk([&](mlir::Operation* o) {
|
| 2671 |
+
+ if (_reported) {
|
| 2672 |
+
+ return;
|
| 2673 |
+
+ }
|
| 2674 |
+
+ for (unsigned i = 0; i < o->getNumOperands(); ++i) {
|
| 2675 |
+
+ if (!o->getOperand(i)) {
|
| 2676 |
+
+ llvm::errs() << "ONW null operand " << i << " of " << o->getName() << " after pass "
|
| 2677 |
+
+ << pass->getName() << " at " << o->getLoc() << "\n";
|
| 2678 |
+
+ _reported = true;
|
| 2679 |
+
+ return;
|
| 2680 |
+
+ }
|
| 2681 |
+
+ }
|
| 2682 |
+
+ });
|
| 2683 |
+
+ }
|
| 2684 |
+
+
|
| 2685 |
+
+private:
|
| 2686 |
+
+ bool _reported = false;
|
| 2687 |
+
+};
|
| 2688 |
+
+
|
| 2689 |
+
void addLogging(mlir::PassManager& pm, Logger log) {
|
| 2690 |
+
pm.addInstrumentation(std::make_unique<PassLogging>(log));
|
| 2691 |
+
+ if (std::getenv("ONW_FIND_NULLOP") != nullptr) {
|
| 2692 |
+
+ pm.addInstrumentation(std::make_unique<OnwNullOperandCheck>());
|
| 2693 |
+
+ }
|
| 2694 |
+
}
|
| 2695 |
+
|
| 2696 |
+
void DeveloperConfig::setup(mlir::PassManager& pm, const vpux::OV::Config& config, bool isSubPipeline) const {
|
| 2697 |
diff --git a/src/vpux_compiler/src/utils/dma_transaction_utils.cpp b/src/vpux_compiler/src/utils/dma_transaction_utils.cpp
|
| 2698 |
index 10cb1f4..45e02e8 100644
|
| 2699 |
--- a/src/vpux_compiler/src/utils/dma_transaction_utils.cpp
|
|
|
|
| 2715 |
VPUX_THROW_WHEN(std::any_of(std::begin(reducedBitStrides), std::prev(std::end(reducedBitStrides)),
|
| 2716 |
[](auto bitStride) {
|
| 2717 |
return bitStride.count() % CHAR_BIT != 0;
|
| 2718 |
+
diff --git a/thirdparty/vpucostmodel b/thirdparty/vpucostmodel
|
| 2719 |
+
--- a/thirdparty/vpucostmodel
|
| 2720 |
+
+++ b/thirdparty/vpucostmodel
|
| 2721 |
+
@@ -1 +1 @@
|
| 2722 |
+
-Subproject commit 4b5da8484d7a5c8cb097486c1248c4d2a753ae4b
|
| 2723 |
+
+Subproject commit 4b5da8484d7a5c8cb097486c1248c4d2a753ae4b-dirty
|
onw/npukit/patches/openvino-6db412d-npu_plugin.patch
CHANGED
|
@@ -13,10 +13,10 @@ index c5f05a69..e976d53b 100644
|
|
| 13 |
* @brief Tensors pointing towards the buffers found in "_mainInputsAllocatedTensors".
|
| 14 |
* @details Each map entry corresponds to one input of the main schedule.
|
| 15 |
diff --git a/src/plugins/intel_npu/src/compiler_adapter/src/weightless_graph.cpp b/src/plugins/intel_npu/src/compiler_adapter/src/weightless_graph.cpp
|
| 16 |
-
index b56d95b5..
|
| 17 |
--- a/src/plugins/intel_npu/src/compiler_adapter/src/weightless_graph.cpp
|
| 18 |
+++ b/src/plugins/intel_npu/src/compiler_adapter/src/weightless_graph.cpp
|
| 19 |
-
@@ -5,
|
| 20 |
#include "weightless_graph.hpp"
|
| 21 |
|
| 22 |
#include <cinttypes>
|
|
@@ -27,8 +27,19 @@ index b56d95b5..24790c3c 100644
|
|
| 27 |
+#include <cstdio>
|
| 28 |
#include <condition_variable>
|
| 29 |
#include <iterator>
|
|
|
|
| 30 |
#include <mutex>
|
| 31 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
|
| 33 |
namespace {
|
| 34 |
|
|
@@ -45,6 +56,10 @@ index b56d95b5..24790c3c 100644
|
|
| 45 |
+};
|
| 46 |
+std::mutex onwWsMutex;
|
| 47 |
+std::unordered_multimap<uint64_t, OnwWsEntry> onwWsRegistry;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
+
|
| 49 |
+uint64_t onwSampledHash(const unsigned char* p, size_t n) {
|
| 50 |
+ uint64_t h = 1469598103934665603ULL ^ n;
|
|
@@ -67,46 +82,98 @@ index b56d95b5..24790c3c 100644
|
|
| 67 |
+ return e == nullptr || std::string(e) != "0";
|
| 68 |
+}
|
| 69 |
+
|
| 70 |
-
+//
|
| 71 |
-
+//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
+std::optional<std::vector<std::shared_ptr<ZeroTensor>>> onwWsShare(
|
|
|
|
| 73 |
+ const std::shared_ptr<ZeroTensor>& hostTensor,
|
| 74 |
+ std::unordered_map<std::string, std::shared_ptr<ov::ITensor>>& tensorsMap) {
|
| 75 |
+ if (!onwWsShareEnabled() || hostTensor == nullptr) {
|
| 76 |
+ return std::nullopt;
|
| 77 |
+ }
|
| 78 |
+ std::lock_guard<std::mutex> lock(onwWsMutex);
|
| 79 |
-
+
|
| 80 |
-
+
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
+ for (const auto& [name, t] : tensorsMap) {
|
| 82 |
+ const auto* p = static_cast<const unsigned char*>(t->data());
|
| 83 |
+ const size_t n = t->get_byte_size();
|
| 84 |
-
+ const uint64_t h = onwSampledHash(p, n);
|
| 85 |
+ bool hit = false;
|
| 86 |
-
+ auto range = onwWsRegistry.equal_range(
|
| 87 |
+ for (auto it = range.first; it != range.second; ++it) {
|
| 88 |
+ auto owner = it->second.owner.lock();
|
| 89 |
+ if (owner == nullptr || owner == hostTensor || it->second.size != n) {
|
| 90 |
+ continue;
|
| 91 |
+ }
|
| 92 |
+ if (std::memcmp(it->second.data, p, n) == 0) {
|
| 93 |
-
+ found.
|
| 94 |
-
+
|
| 95 |
+ hit = true;
|
| 96 |
+ break;
|
| 97 |
+ }
|
| 98 |
+ }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 99 |
+ if (!hit) {
|
| 100 |
-
+
|
|
|
|
|
|
|
|
|
|
| 101 |
+ }
|
| 102 |
+ }
|
| 103 |
-
+
|
| 104 |
-
+
|
| 105 |
-
+
|
|
|
|
|
|
|
|
|
|
|
|
|
| 106 |
+ const auto* p = static_cast<const unsigned char*>(t->data());
|
| 107 |
+ const size_t n = t->get_byte_size();
|
| 108 |
-
+ onwWsRegistry.emplace(onwSampledHash(p, n), OnwWsEntry{
|
|
|
|
|
|
|
|
|
|
| 109 |
+ }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 110 |
+ if (log) {
|
| 111 |
+ std::fprintf(stderr, "[onw ws-share] new weights: %zu outputs, %.1f MB%c", tensorsMap.size(),
|
| 112 |
+ hostTensor->get_byte_size() / 1e6, 10);
|
|
@@ -120,6 +187,40 @@ index b56d95b5..24790c3c 100644
|
|
| 120 |
+ const_cast<unsigned char*>(src.second));
|
| 121 |
+ owners.push_back(src.first);
|
| 122 |
+ }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 123 |
+ if (log) {
|
| 124 |
+ std::fprintf(stderr, "[onw ws-share] shared: %zu outputs, %.1f MB released%c", found.size(),
|
| 125 |
+ hostTensor->get_byte_size() / 1e6, 10);
|
|
@@ -131,7 +232,7 @@ index b56d95b5..24790c3c 100644
|
|
| 131 |
constexpr uint8_t MAIN_SCHEDULE_INDEX = 0;
|
| 132 |
constexpr std::string_view WEIGHTS_IR_EXTENSION = ".bin";
|
| 133 |
constexpr std::string_view WEIGHTS_ONNX_EXTENSION = ".data";
|
| 134 |
-
@@ -423,6 +
|
| 135 |
_initsFences.clear();
|
| 136 |
_initsMetadata.clear();
|
| 137 |
_initsCommandQueue.reset();
|
|
@@ -139,7 +240,7 @@ index b56d95b5..24790c3c 100644
|
|
| 139 |
|
| 140 |
// The main schedule is initialized after the weights initialization ones in order to save some memory
|
| 141 |
Graph::initialize_impl(config);
|
| 142 |
-
@@ -502,7 +
|
| 143 |
// model appears in exactly one schedule. Thus, one can delete
|
| 144 |
// the handle to the constant memory early.
|
| 145 |
if (auto it = constants.find(id); it != constants.end()) {
|
|
@@ -154,11 +255,40 @@ index b56d95b5..24790c3c 100644
|
|
| 154 |
constants.erase(it);
|
| 155 |
}
|
| 156 |
}
|
| 157 |
-
@@ -
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 158 |
create_pipeline(initIndex, initInputsViewTensors, initOutputsViewTensors);
|
| 159 |
run_pipeline(initIndex);
|
| 160 |
|
| 161 |
-
+ if (auto owners = onwWsShare(initOutputsAllocatedTensor, initOutputsViewTensorsMap)) {
|
| 162 |
+ merge_two_maps(_mainInputsViewTensors, initOutputsViewTensorsMap);
|
| 163 |
+ for (auto& o : *owners) {
|
| 164 |
+ _mainInputsAllocatedTensors.push_back(o);
|
|
@@ -169,11 +299,11 @@ index b56d95b5..24790c3c 100644
|
|
| 169 |
merge_two_maps(_mainInputsViewTensors, initOutputsViewTensorsMap);
|
| 170 |
_mainInputsAllocatedTensors.push_back(std::move(initOutputsAllocatedTensor));
|
| 171 |
}
|
| 172 |
-
@@ -600,6 +
|
| 173 |
run_pipeline(data.initIndex);
|
| 174 |
|
| 175 |
// TODO: pre-allocate those well in advance? (outside of this loop)
|
| 176 |
-
+ if (auto owners = onwWsShare(data.outputs.hostTensor, data.outputs.tensorsMap)) {
|
| 177 |
+ merge_two_maps(_mainInputsViewTensors, data.outputs.tensorsMap);
|
| 178 |
+ for (auto& o : *owners) {
|
| 179 |
+ _mainInputsAllocatedTensors.push_back(o);
|
|
|
|
| 13 |
* @brief Tensors pointing towards the buffers found in "_mainInputsAllocatedTensors".
|
| 14 |
* @details Each map entry corresponds to one input of the main schedule.
|
| 15 |
diff --git a/src/plugins/intel_npu/src/compiler_adapter/src/weightless_graph.cpp b/src/plugins/intel_npu/src/compiler_adapter/src/weightless_graph.cpp
|
| 16 |
+
index b56d95b5..2d43d8fe 100644
|
| 17 |
--- a/src/plugins/intel_npu/src/compiler_adapter/src/weightless_graph.cpp
|
| 18 |
+++ b/src/plugins/intel_npu/src/compiler_adapter/src/weightless_graph.cpp
|
| 19 |
+
@@ -5,8 +5,14 @@
|
| 20 |
#include "weightless_graph.hpp"
|
| 21 |
|
| 22 |
#include <cinttypes>
|
|
|
|
| 27 |
+#include <cstdio>
|
| 28 |
#include <condition_variable>
|
| 29 |
#include <iterator>
|
| 30 |
+
+#include <map>
|
| 31 |
#include <mutex>
|
| 32 |
+
#include <queue>
|
| 33 |
+
|
| 34 |
+
@@ -24,12 +30,200 @@
|
| 35 |
+
#include "openvino/util/common_util.hpp"
|
| 36 |
+
#include "openvino/util/mmap_object.hpp"
|
| 37 |
+
|
| 38 |
+
+#include <algorithm>
|
| 39 |
+
+
|
| 40 |
+
#define USE_SINGLE_THREADED_RUN_INIT 1
|
| 41 |
+
|
| 42 |
+
namespace intel_npu {
|
| 43 |
|
| 44 |
namespace {
|
| 45 |
|
|
|
|
| 56 |
+};
|
| 57 |
+std::mutex onwWsMutex;
|
| 58 |
+std::unordered_multimap<uint64_t, OnwWsEntry> onwWsRegistry;
|
| 59 |
+
+// outputs of 64 KB and more, for outputs that are a part of one (onw 3.2): a 64-token graph keeps a 2-bit weight per
|
| 60 |
+
+// group, the one-token graph the same groups merged k at a time - group g of a merged weight is its bytes at
|
| 61 |
+
+// (g - g0) x the group's size. ONW_WS_SUBRANGE=0: whole outputs only.
|
| 62 |
+
+std::vector<OnwWsEntry> onwWsBig;
|
| 63 |
+
|
| 64 |
+uint64_t onwSampledHash(const unsigned char* p, size_t n) {
|
| 65 |
+ uint64_t h = 1469598103934665603ULL ^ n;
|
|
|
|
| 82 |
+ return e == nullptr || std::string(e) != "0";
|
| 83 |
+}
|
| 84 |
+
|
| 85 |
+
+// Per output (onw 3.2): every output of the schedule is looked up on its own. Found ones are bound to the existing
|
| 86 |
+
+// buffer; when all were found the new buffer is released as before; when only some were, the others are copied into a
|
| 87 |
+
+// compact buffer of their own (registered for later graphs) and the big one is released too - so graphs that hold the
|
| 88 |
+
+// same weights in a different set (the 1-token and the 64-token graphs of a 2-bit model: one output per weight group,
|
| 89 |
+
+// split differently over their init schedules) share every tensor they have in common. Returns the buffers to keep
|
| 90 |
+
+// alive when the new buffer can go, else registers its outputs and returns nothing (ONW_WS_SHARE_PARTIAL=0: all or
|
| 91 |
+
+// nothing, as before).
|
| 92 |
+std::optional<std::vector<std::shared_ptr<ZeroTensor>>> onwWsShare(
|
| 93 |
+
+ const std::shared_ptr<ZeroInitStructsHolder>& initStruct,
|
| 94 |
+ const std::shared_ptr<ZeroTensor>& hostTensor,
|
| 95 |
+ std::unordered_map<std::string, std::shared_ptr<ov::ITensor>>& tensorsMap) {
|
| 96 |
+ if (!onwWsShareEnabled() || hostTensor == nullptr) {
|
| 97 |
+ return std::nullopt;
|
| 98 |
+ }
|
| 99 |
+ std::lock_guard<std::mutex> lock(onwWsMutex);
|
| 100 |
+
+ const bool log = std::getenv("ONW_WS_SHARE_LOG") != nullptr;
|
| 101 |
+
+ const char* pe = std::getenv("ONW_WS_SHARE_PARTIAL");
|
| 102 |
+
+ const bool partial = pe == nullptr || std::string(pe) != "0";
|
| 103 |
+
+ // found[name] = (owner, data) of an identical live buffer
|
| 104 |
+
+ std::unordered_map<std::string, std::pair<std::shared_ptr<ZeroTensor>, const unsigned char*>> found;
|
| 105 |
+
+ size_t foundBytes = 0, restBytes = 0;
|
| 106 |
+ for (const auto& [name, t] : tensorsMap) {
|
| 107 |
+ const auto* p = static_cast<const unsigned char*>(t->data());
|
| 108 |
+ const size_t n = t->get_byte_size();
|
|
|
|
| 109 |
+ bool hit = false;
|
| 110 |
+
+ auto range = onwWsRegistry.equal_range(onwSampledHash(p, n));
|
| 111 |
+ for (auto it = range.first; it != range.second; ++it) {
|
| 112 |
+ auto owner = it->second.owner.lock();
|
| 113 |
+ if (owner == nullptr || owner == hostTensor || it->second.size != n) {
|
| 114 |
+ continue;
|
| 115 |
+ }
|
| 116 |
+ if (std::memcmp(it->second.data, p, n) == 0) {
|
| 117 |
+
+ found.emplace(name, std::make_pair(owner, it->second.data));
|
| 118 |
+
+ foundBytes += n;
|
| 119 |
+ hit = true;
|
| 120 |
+ break;
|
| 121 |
+ }
|
| 122 |
+ }
|
| 123 |
+
+ static const bool subrange = std::getenv("ONW_WS_SUBRANGE") == nullptr ||
|
| 124 |
+
+ std::string(std::getenv("ONW_WS_SUBRANGE")) != "0";
|
| 125 |
+
+ if (!hit && subrange && partial && n >= (size_t(1) << 15)) {
|
| 126 |
+
+ for (const auto& big : onwWsBig) {
|
| 127 |
+
+ if (big.size <= n || big.size % n != 0) {
|
| 128 |
+
+ continue;
|
| 129 |
+
+ }
|
| 130 |
+
+ auto owner = big.owner.lock();
|
| 131 |
+
+ if (owner == nullptr || owner == hostTensor) {
|
| 132 |
+
+ continue;
|
| 133 |
+
+ }
|
| 134 |
+
+ for (size_t off = 0; off + n <= big.size; off += n) {
|
| 135 |
+
+ if (std::memcmp(big.data + off, p, 64) == 0 && std::memcmp(big.data + off, p, n) == 0) {
|
| 136 |
+
+ found.emplace(name, std::make_pair(owner, big.data + off));
|
| 137 |
+
+ foundBytes += n;
|
| 138 |
+
+ hit = true;
|
| 139 |
+
+ break;
|
| 140 |
+
+ }
|
| 141 |
+
+ }
|
| 142 |
+
+ if (hit) {
|
| 143 |
+
+ break;
|
| 144 |
+
+ }
|
| 145 |
+
+ }
|
| 146 |
+
+ }
|
| 147 |
+ if (!hit) {
|
| 148 |
+
+ restBytes += n;
|
| 149 |
+
+ if (!partial) {
|
| 150 |
+
+ break;
|
| 151 |
+
+ }
|
| 152 |
+ }
|
| 153 |
+ }
|
| 154 |
+
+ auto registerAll = [&](const std::shared_ptr<ZeroTensor>& owner,
|
| 155 |
+
+ const std::unordered_map<std::string, std::shared_ptr<ov::ITensor>>& m,
|
| 156 |
+
+ const std::unordered_map<std::string, bool>* only) {
|
| 157 |
+
+ for (const auto& [name, t] : m) {
|
| 158 |
+
+ if (only != nullptr && only->count(name) == 0) {
|
| 159 |
+
+ continue;
|
| 160 |
+
+ }
|
| 161 |
+ const auto* p = static_cast<const unsigned char*>(t->data());
|
| 162 |
+ const size_t n = t->get_byte_size();
|
| 163 |
+
+ onwWsRegistry.emplace(onwSampledHash(p, n), OnwWsEntry{owner, p, n});
|
| 164 |
+
+ if (n >= (size_t(1) << 16)) {
|
| 165 |
+
+ onwWsBig.push_back(OnwWsEntry{owner, p, n});
|
| 166 |
+
+ }
|
| 167 |
+ }
|
| 168 |
+
+ // (entries of released buffers go)
|
| 169 |
+
+ onwWsBig.erase(std::remove_if(onwWsBig.begin(), onwWsBig.end(),
|
| 170 |
+
+ [](const OnwWsEntry& e) {
|
| 171 |
+
+ return e.owner.expired();
|
| 172 |
+
+ }),
|
| 173 |
+
+ onwWsBig.end());
|
| 174 |
+
+ };
|
| 175 |
+
+ if (found.empty() || (!partial && found.size() != tensorsMap.size())) {
|
| 176 |
+
+ registerAll(hostTensor, tensorsMap, nullptr);
|
| 177 |
+ if (log) {
|
| 178 |
+ std::fprintf(stderr, "[onw ws-share] new weights: %zu outputs, %.1f MB%c", tensorsMap.size(),
|
| 179 |
+ hostTensor->get_byte_size() / 1e6, 10);
|
|
|
|
| 187 |
+ const_cast<unsigned char*>(src.second));
|
| 188 |
+ owners.push_back(src.first);
|
| 189 |
+ }
|
| 190 |
+
+ if (found.size() != tensorsMap.size()) {
|
| 191 |
+
+ // the outputs not found: copied into a compact buffer (each at a 64-byte aligned offset)
|
| 192 |
+
+ auto align = [](size_t v) {
|
| 193 |
+
+ return (v + 63) & ~size_t(63);
|
| 194 |
+
+ };
|
| 195 |
+
+ size_t total = 0;
|
| 196 |
+
+ for (const auto& [name, t] : tensorsMap) {
|
| 197 |
+
+ if (found.count(name) == 0) {
|
| 198 |
+
+ total += align(t->get_byte_size());
|
| 199 |
+
+ }
|
| 200 |
+
+ }
|
| 201 |
+
+ auto compact = std::make_shared<ZeroTensor>(initStruct, ov::element::Type_t::u8, ov::Shape({total}), false);
|
| 202 |
+
+ auto* base = static_cast<unsigned char*>(const_cast<void*>(compact->data(ov::element::Type_t::u8)));
|
| 203 |
+
+ size_t off = 0;
|
| 204 |
+
+ std::unordered_map<std::string, bool> moved;
|
| 205 |
+
+ for (auto& [name, t] : tensorsMap) {
|
| 206 |
+
+ if (found.count(name) != 0) {
|
| 207 |
+
+ continue;
|
| 208 |
+
+ }
|
| 209 |
+
+ const size_t n = t->get_byte_size();
|
| 210 |
+
+ std::memcpy(base + off, t->data(), n);
|
| 211 |
+
+ t = ov::make_tensor(t->get_element_type(), t->get_shape(), base + off);
|
| 212 |
+
+ moved.emplace(name, true);
|
| 213 |
+
+ off += align(n);
|
| 214 |
+
+ }
|
| 215 |
+
+ registerAll(compact, tensorsMap, &moved);
|
| 216 |
+
+ owners.push_back(compact);
|
| 217 |
+
+ if (log) {
|
| 218 |
+
+ std::fprintf(stderr, "[onw ws-share] partly shared: %zu of %zu outputs, %.1f MB shared, %.1f MB kept "
|
| 219 |
+
+ "(of %.1f MB)%c", found.size(), tensorsMap.size(), foundBytes / 1e6, total / 1e6,
|
| 220 |
+
+ hostTensor->get_byte_size() / 1e6, 10);
|
| 221 |
+
+ }
|
| 222 |
+
+ return owners;
|
| 223 |
+
+ }
|
| 224 |
+ if (log) {
|
| 225 |
+ std::fprintf(stderr, "[onw ws-share] shared: %zu outputs, %.1f MB released%c", found.size(),
|
| 226 |
+ hostTensor->get_byte_size() / 1e6, 10);
|
|
|
|
| 232 |
constexpr uint8_t MAIN_SCHEDULE_INDEX = 0;
|
| 233 |
constexpr std::string_view WEIGHTS_IR_EXTENSION = ".bin";
|
| 234 |
constexpr std::string_view WEIGHTS_ONNX_EXTENSION = ".data";
|
| 235 |
+
@@ -423,6 +617,7 @@ void WeightlessGraph::initialize_impl(const FilteredConfig& config) {
|
| 236 |
_initsFences.clear();
|
| 237 |
_initsMetadata.clear();
|
| 238 |
_initsCommandQueue.reset();
|
|
|
|
| 240 |
|
| 241 |
// The main schedule is initialized after the weights initialization ones in order to save some memory
|
| 242 |
Graph::initialize_impl(config);
|
| 243 |
+
@@ -502,7 +697,13 @@ WeightlessGraph::InputData WeightlessGraph::allocate_inputs(
|
| 244 |
// model appears in exactly one schedule. Thus, one can delete
|
| 245 |
// the handle to the constant memory early.
|
| 246 |
if (auto it = constants.find(id); it != constants.end()) {
|
|
|
|
| 255 |
constants.erase(it);
|
| 256 |
}
|
| 257 |
}
|
| 258 |
+
@@ -525,6 +726,28 @@ WeightlessGraph::OutputData WeightlessGraph::allocate_outputs(const size_t initI
|
| 259 |
+
initOutputsByteSize += size.value();
|
| 260 |
+
}
|
| 261 |
+
|
| 262 |
+
+ if (std::getenv("ONW_WS_SHARE_LOG") != nullptr && initOutputsByteSize >= (size_t(1) << 30)) {
|
| 263 |
+
+ std::map<std::string, std::pair<size_t, size_t>> byType; // type -> (count, bytes)
|
| 264 |
+
+ for (const IODescriptor& descriptor : _initsMetadata.at(initIndex).outputs) {
|
| 265 |
+
+ auto& e = byType[descriptor.precision.to_string()];
|
| 266 |
+
+ e.first += 1;
|
| 267 |
+
+ e.second += ov::util::get_memory_size(descriptor.precision, shape_size(descriptor.shapeFromCompiler.to_shape()));
|
| 268 |
+
+ }
|
| 269 |
+
+ std::fprintf(stderr, "[onw ws-share] init %zu outputs: %zu, %.1f MB%c", initIndex,
|
| 270 |
+
+ _initsMetadata.at(initIndex).outputs.size(), initOutputsByteSize / 1e6, 10);
|
| 271 |
+
+ for (const auto& [t, e] : byType) {
|
| 272 |
+
+ std::fprintf(stderr, "[onw ws-share] type %s: %zu outputs, %.1f MB%c", t.c_str(), e.first, e.second / 1e6,
|
| 273 |
+
+ 10);
|
| 274 |
+
+ }
|
| 275 |
+
+ size_t k = 0;
|
| 276 |
+
+ for (const IODescriptor& descriptor : _initsMetadata.at(initIndex).outputs) {
|
| 277 |
+
+ if (k++ < 4) {
|
| 278 |
+
+ std::fprintf(stderr, "[onw ws-share] e.g. %s %s %s%c", descriptor.nameFromCompiler.c_str(),
|
| 279 |
+
+ descriptor.precision.to_string().c_str(),
|
| 280 |
+
+ descriptor.shapeFromCompiler.to_string().c_str(), 10);
|
| 281 |
+
+ }
|
| 282 |
+
+ }
|
| 283 |
+
+ }
|
| 284 |
+
const std::shared_ptr<ZeroTensor> initOutputsAllocatedTensor =
|
| 285 |
+
std::make_shared<ZeroTensor>(_zeroInitStruct, ov::element::Type_t::u8, ov::Shape({initOutputsByteSize}), false);
|
| 286 |
+
|
| 287 |
+
@@ -557,6 +780,14 @@ void WeightlessGraph::run_init_single_threaded() {
|
| 288 |
create_pipeline(initIndex, initInputsViewTensors, initOutputsViewTensors);
|
| 289 |
run_pipeline(initIndex);
|
| 290 |
|
| 291 |
+
+ if (auto owners = onwWsShare(_zeroInitStruct, initOutputsAllocatedTensor, initOutputsViewTensorsMap)) {
|
| 292 |
+ merge_two_maps(_mainInputsViewTensors, initOutputsViewTensorsMap);
|
| 293 |
+ for (auto& o : *owners) {
|
| 294 |
+ _mainInputsAllocatedTensors.push_back(o);
|
|
|
|
| 299 |
merge_two_maps(_mainInputsViewTensors, initOutputsViewTensorsMap);
|
| 300 |
_mainInputsAllocatedTensors.push_back(std::move(initOutputsAllocatedTensor));
|
| 301 |
}
|
| 302 |
+
@@ -600,6 +831,14 @@ void WeightlessGraph::run_init_multi_threaded() {
|
| 303 |
run_pipeline(data.initIndex);
|
| 304 |
|
| 305 |
// TODO: pre-allocate those well in advance? (outside of this loop)
|
| 306 |
+
+ if (auto owners = onwWsShare(_zeroInitStruct, data.outputs.hostTensor, data.outputs.tensorsMap)) {
|
| 307 |
+ merge_two_maps(_mainInputsViewTensors, data.outputs.tensorsMap);
|
| 308 |
+ for (auto& o : *owners) {
|
| 309 |
+ _mainInputsAllocatedTensors.push_back(o);
|
onw/runtime.py
CHANGED
|
@@ -311,7 +311,9 @@ def all_sizes(meta):
|
|
| 311 |
"""[2.0] Every block size the model folder has graphs for: "sizes" (what onw before 2.0 reads) + "prompt_sizes"
|
| 312 |
(larger prompt blocks added to a published model later, see compact.graft_sizes - out of older onw's sight, whose
|
| 313 |
loader cannot read their seg<i>x.bin)."""
|
| 314 |
-
|
|
|
|
|
|
|
| 315 |
|
| 316 |
|
| 317 |
_CONST_RE = re.compile(rb'<layer id="\d+" name="([^"]+)" type="Const"([^>]*)>(\s*)<data ([^>]*?)offset="(\d+)" size="(\d+)"([^>]*)/>')
|
|
@@ -348,6 +350,14 @@ def _xbin_model(core, d, name, m, weights):
|
|
| 348 |
return model
|
| 349 |
|
| 350 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 351 |
def read_segment(core, d, name, m, npuw, bins, T, built_T):
|
| 352 |
"""The segment's ov.Model, reshaped to context T. With NPUW every segment of a .bin reads against the same
|
| 353 |
in-memory copy (bins cache), so the weights bank sees one set of constants."""
|
|
@@ -358,23 +368,65 @@ def read_segment(core, d, name, m, npuw, bins, T, built_T):
|
|
| 358 |
model = _xbin_model(core, d, name, m, bins[b])
|
| 359 |
if npuw:
|
| 360 |
model = _no_bool_constants(model)
|
| 361 |
-
elif npuw:
|
| 362 |
b = m.get("bin") or name[:-4] + ".bin"
|
| 363 |
if b not in bins:
|
| 364 |
# a file-backed mapping (OpenVINO shares it, see mapped_bin): NPUW's weights bank keeps reading the
|
| 365 |
# constants after compiling, so the buffer lives as long as the model (SegmentedModel._bins), and as
|
| 366 |
# mapped pages it costs no private memory
|
| 367 |
bins[b] = ov.Tensor(mapped_bin(os.path.join(d, b)), shared_memory=True)
|
| 368 |
-
model =
|
|
|
|
|
|
|
| 369 |
else:
|
| 370 |
model = core.read_model(os.path.join(d, name), os.path.join(d, m["bin"]) if "bin" in m else None)
|
| 371 |
if T != built_T:
|
| 372 |
model.reshape(reshaped_inputs(model, m["S"], built_T, T))
|
| 373 |
if os.environ.get("ONW_CODES_SCALE") == "const" and codes_const_scale(model): # (opt-in, see there; marked
|
| 374 |
model.set_rt_info("1", "onw_codes_const") # for the compile keys)
|
|
|
|
|
|
|
| 375 |
return model
|
| 376 |
|
| 377 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 378 |
GROUPED_MIN = int(os.environ.get("ONW_GROUPED_MIN", "2")) # [2.0] prompts from this long go the grouped-MoE path
|
| 379 |
GROUPED_RAM_GB = 24 # [2.0] RAM the grouped-MoE prompt path needs
|
| 380 |
|
|
@@ -944,6 +996,13 @@ class _Tried:
|
|
| 944 |
pass
|
| 945 |
|
| 946 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 947 |
class Precompiled(Exception):
|
| 948 |
"""Raised by a precompile child (ONW_PRECOMPILE_ONLY) once its segments are in the compile cache."""
|
| 949 |
|
|
@@ -962,9 +1021,34 @@ def compile_jobs(core, device, npuw):
|
|
| 962 |
(Panther Lake, Ornith-1.5-9B NF4: 8.4 instead of 5.2 GB of NPU memory, onw 1.4.2 / 1.4.3 did this by default).
|
| 963 |
Each child holds about 3.5 GB at its peak."""
|
| 964 |
env = os.environ.get("ONW_COMPILE_JOBS")
|
| 965 |
-
if
|
| 966 |
return 1
|
| 967 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 968 |
|
| 969 |
|
| 970 |
def setup_mark(cache, config, tried_key):
|
|
@@ -1059,12 +1143,19 @@ class SegmentedModel:
|
|
| 1059 |
config = enable_turbo(core, device, config) # (see turbo(): on unless ONW_TURBO=0)
|
| 1060 |
config = enable_compiler(core, device, config) # [2.0.3] (see npu_compiler())
|
| 1061 |
t0 = time.time()
|
| 1062 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1063 |
self.bank, self.E, self.K = self._expert_bank(core, d, device, sizes)
|
| 1064 |
else: # dense model
|
| 1065 |
self.bank, self.E, self.K, self.streaming = {}, 0, 0, False
|
| 1066 |
sp = os.path.join(d, "shared.bin")
|
| 1067 |
-
|
|
|
|
| 1068 |
self.embed_rows = self._table("embed")
|
| 1069 |
self.window = self.meta.get("masks", {}).get("sliding_window")
|
| 1070 |
self.host_inputs = {}
|
|
@@ -1089,7 +1180,8 @@ class SegmentedModel:
|
|
| 1089 |
# INT4 weights of the plain paged models as well: never separated by the compiler (Granite 4.2 3B INT4 part:
|
| 1090 |
# 63.5 MB weightless against 63.6 MB, multi-row parts 20-35x slower to compile) - INT8 and NF4 are (llm-jp INT8
|
| 1091 |
# part: 2.5 against 216 MB). (NPUW and MoE models as they were checked: gemma-4, Qwen3.6)
|
| 1092 |
-
|
|
|
|
| 1093 |
_WEIGHTLESS["full"] = False # (this model: a part with its weights inside, see fused.wanted)
|
| 1094 |
if device == "NPU" and not _WEIGHTLESS.get("told"):
|
| 1095 |
from . import npukit
|
|
@@ -1107,7 +1199,7 @@ class SegmentedModel:
|
|
| 1107 |
print(f"[npuw] off: ~{parts} compiled parts (each attention layer cuts a segment) - more than the NPU driver "
|
| 1108 |
f"holds with NPUW (~30); ONW_NPUW=1 forces it", flush=True)
|
| 1109 |
npuw = False
|
| 1110 |
-
if not npuw and not self.bank and int4_weights(d, self.meta) and os.environ.get("
|
| 1111 |
_WEIGHTLESS["skip_model"] = True # (decided here: NPUW may have been turned off just above)
|
| 1112 |
if npuw:
|
| 1113 |
# experimental: NPUW pulls the weights out of every compiled graph into one weights bank; constants match
|
|
@@ -1198,9 +1290,16 @@ class SegmentedModel:
|
|
| 1198 |
and os.path.exists(os.path.join(d, m.get("bin") or n[:-4] + ".bin"))] or [0.0])
|
| 1199 |
npuw_check_runs(cache, graphs, covered, newest)
|
| 1200 |
jobs = compile_jobs(core, device, npuw) if cache and own and not (only or precompile) else 1
|
|
|
|
|
|
|
|
|
|
| 1201 |
if jobs > 1 and mark and not covered:
|
| 1202 |
self._precompile(jobs, sorted(n for n in self.meta["segments"] if n[:-4] in graphs), todo)
|
| 1203 |
tried = self._ctx["tried"] = _Tried(config, core, device, npuw) # (what the children recorded)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1204 |
for attempt in range(2):
|
| 1205 |
k, tc = 0, time.time()
|
| 1206 |
for name, m in sorted(self.meta["segments"].items()):
|
|
@@ -1275,6 +1374,8 @@ class SegmentedModel:
|
|
| 1275 |
runs = set(NPUW_SEEN.values())
|
| 1276 |
if mark and not (NPUW_SEEN and (len(runs) > 1 or None in runs)): # (every graph is in the cache, one run)
|
| 1277 |
write_mark(mark, graphs)
|
|
|
|
|
|
|
| 1278 |
# the MTP layer (draft tokens, Qwen3.5 dense): compiled without NPUW, before the paged KV cache is set up
|
| 1279 |
# (its attention layer gets a cache of its own); mtp: wanted (chat.use_mtp)
|
| 1280 |
self.mtp, self.last_h = None, None
|
|
@@ -1330,7 +1431,12 @@ class SegmentedModel:
|
|
| 1330 |
# memory-saving mode (ONW_LAZY16=1): block sizes come and go, so the state lives in tensors of its own (else in
|
| 1331 |
# the smallest size's requests)
|
| 1332 |
self.swapping = bool(deferred)
|
| 1333 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1334 |
for req, m, names in self.segs[min(self.segs)]:
|
| 1335 |
for n in names:
|
| 1336 |
if n.startswith(("past_k.", "past_v.", "conv.", "ssm.")):
|
|
@@ -1400,8 +1506,10 @@ class SegmentedModel:
|
|
| 1400 |
def fused_keep_all(self):
|
| 1401 |
"""[3.0] every fused variant stays loaded (onw's NPU kit, NPU 4000+, not the memory-saving mode)."""
|
| 1402 |
from . import npukit
|
|
|
|
|
|
|
| 1403 |
return os.environ.get("ONW_FUSED_KEEP", "1" if npukit.active() and npu_arch(self._ctx["core"]) >= 4000
|
| 1404 |
-
and not mem_saver() else "0") == "1"
|
| 1405 |
|
| 1406 |
def fused_prepare(self, need, S=None):
|
| 1407 |
"""Make the fused S-token graphs (default: the answer size) covering `need` positions the ones in use for that
|
|
@@ -1444,6 +1552,9 @@ class SegmentedModel:
|
|
| 1444 |
return None
|
| 1445 |
F = next((keep[(S, B)] for B in bs if B >= want and (S, B) in keep), None)
|
| 1446 |
if F is None:
|
|
|
|
|
|
|
|
|
|
| 1447 |
with self._mutex:
|
| 1448 |
try:
|
| 1449 |
F = keep[(S, want)] = _f.FusedDecode(self, S, want)
|
|
@@ -1452,6 +1563,7 @@ class SegmentedModel:
|
|
| 1452 |
self._fused_sizes = [x for x in sizes if x != S]
|
| 1453 |
return None
|
| 1454 |
self._fused[S] = F
|
|
|
|
| 1455 |
self.S = max([*self.segs, *self._fused])
|
| 1456 |
self._fused_background(caps)
|
| 1457 |
return F
|
|
@@ -1467,6 +1579,9 @@ class SegmentedModel:
|
|
| 1467 |
return None
|
| 1468 |
if F is not None and F.B >= want:
|
| 1469 |
return F
|
|
|
|
|
|
|
|
|
|
| 1470 |
F = None # [2.1.0] (this reference kept the old variant alive while the
|
| 1471 |
with self._mutex: # next one loaded: peak working set +3.3 GB, NPU 3720 Granite 3B)
|
| 1472 |
self._fused.pop(S, None) # (out first: never two copies of the weights)
|
|
@@ -1490,6 +1605,44 @@ class SegmentedModel:
|
|
| 1490 |
self._fused_background(caps)
|
| 1491 |
return self._fused[S]
|
| 1492 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1493 |
def _fused_background(self, caps):
|
| 1494 |
npuw_ = (self._ctx["config"] or {}).get("NPU_USE_NPUW") == "YES" # (NPUW: compiled when needed, no background)
|
| 1495 |
if os.environ.get("ONW_FUSED_BG", "1") != "0" and not self._fused_bg and not npuw_:
|
|
@@ -1509,14 +1662,17 @@ class SegmentedModel:
|
|
| 1509 |
env = os.environ.get("ONW_FUSED_JOBS")
|
| 1510 |
if env:
|
| 1511 |
return max(1, int(env))
|
| 1512 |
-
|
| 1513 |
-
|
|
|
|
|
|
|
| 1514 |
try:
|
| 1515 |
import psutil
|
| 1516 |
free = psutil.virtual_memory().available / 2**30
|
| 1517 |
except Exception:
|
| 1518 |
return 1
|
| 1519 |
-
|
|
|
|
| 1520 |
|
| 1521 |
def _fused_precompile(self, jobs):
|
| 1522 |
from . import fused as _f
|
|
@@ -1546,7 +1702,10 @@ class SegmentedModel:
|
|
| 1546 |
cfg = {k: v for k, v in (c["config"] or {}).items()}
|
| 1547 |
kt = self.kv.kt.get_type_name()
|
| 1548 |
env = dict(os.environ, ONW_WS_NO_CONCAT=os.environ.get("ONW_WS_NO_CONCAT", "1"))
|
| 1549 |
-
|
|
|
|
|
|
|
|
|
|
| 1550 |
running, ok = [], True
|
| 1551 |
while todo or running:
|
| 1552 |
for p in [p for p in running if p.poll() is not None]:
|
|
@@ -1555,11 +1714,11 @@ class SegmentedModel:
|
|
| 1555 |
if not ok:
|
| 1556 |
break
|
| 1557 |
if todo and len(running) < n and (not running or self._fused_jobs_parallel() > len(running)):
|
| 1558 |
-
S, B = todo.pop(0)
|
| 1559 |
arg = json.dumps([c["d"], c["device"], cfg, c["tried"].key, c["built_T"], self.kv.capacity, kt, S, B],
|
| 1560 |
default=str)
|
| 1561 |
flags = 0x00004000 if os.name == "nt" else 0 # (BELOW_NORMAL_PRIORITY_CLASS: answers go first)
|
| 1562 |
-
running.append(subprocess.Popen([sys.executable, "-m", "onw.fusedjob", arg], env=env,
|
| 1563 |
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
|
| 1564 |
creationflags=flags))
|
| 1565 |
continue
|
|
@@ -1568,7 +1727,7 @@ class SegmentedModel:
|
|
| 1568 |
p.wait()
|
| 1569 |
if not ok:
|
| 1570 |
print("[fused] a background compile process failed: the rest one after another here", flush=True)
|
| 1571 |
-
for S, B in todo:
|
| 1572 |
try:
|
| 1573 |
from . import fused as _f
|
| 1574 |
_f.FusedDecode(self, S, B, compile_only=True, log=lambda *a, **k: None)
|
|
@@ -1733,12 +1892,17 @@ class SegmentedModel:
|
|
| 1733 |
lg[np.isin(self.mtp["vocab"], self.banned)] = -np.inf
|
| 1734 |
return int(self.mtp["vocab"][int(lg.argmax())])
|
| 1735 |
|
| 1736 |
-
def _precompile(self, jobs, names, todo):
|
| 1737 |
"""Compile these segments into the cache in child processes, `jobs` at a time (see compile_jobs); the load
|
| 1738 |
-
then imports them. A child that fails leaves its segment to the load (compiled there, as without this).
|
|
|
|
| 1739 |
import subprocess, sys
|
| 1740 |
kw = {"creationflags": subprocess.CREATE_NO_WINDOW} if os.name == "nt" else {}
|
| 1741 |
env = {**os.environ, "PYTHONUTF8": "1", "ONW_NPUW_SESSION": NPUW_SESSION}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1742 |
args = json.dumps(self._args)
|
| 1743 |
print(f"[compile] {len(names)} segments, {jobs} processes at a time", flush=True)
|
| 1744 |
t0, waiting, running, done = time.time(), list(names), [], 0
|
|
@@ -1812,7 +1976,7 @@ class SegmentedModel:
|
|
| 1812 |
the expert bank; note which carried values each segment's successors still read."""
|
| 1813 |
for req, m, names in lst:
|
| 1814 |
for n in names:
|
| 1815 |
-
if n in self._owner and (self.
|
| 1816 |
req.set_tensor(n, self._owner[n])
|
| 1817 |
elif n.startswith("shared.") and n[7:] in self.shared:
|
| 1818 |
req.set_tensor(n, self.shared[n[7:]])
|
|
@@ -2013,6 +2177,8 @@ class SegmentedModel:
|
|
| 2013 |
[xml, size, S, "paged-1", part, ring, _signature(model), tried.key]
|
| 2014 |
if part is None and model.has_rt_info("onw_codes_const"): # (a part's signature tells the forms apart)
|
| 2015 |
src.append("codes-const")
|
|
|
|
|
|
|
| 2016 |
# DeltaNet parts of the 4- and 64-token blocks (Qwen3.5): REP folds the repeated rows / chunks inside a layer
|
| 2017 |
# into graphs of undecided shape, which no NPU compiler takes (2-6 s lost per part, an empty cache folder
|
| 2018 |
# left); written so that REP takes them, they ran 6-12% slower than as one graph (Panther Lake) - so NONE first
|
|
@@ -2050,6 +2216,8 @@ class SegmentedModel:
|
|
| 2050 |
except OSError:
|
| 2051 |
pass
|
| 2052 |
if bpw and os.path.exists(bpw) and weightless_wanted():
|
|
|
|
|
|
|
| 2053 |
try:
|
| 2054 |
req = import_blob(core, bpw, device, {**nocache, "MODEL_PTR": model}).create_infer_request()
|
| 2055 |
USED_BLOBS.add(bpw)
|
|
@@ -2065,6 +2233,8 @@ class SegmentedModel:
|
|
| 2065 |
except OSError:
|
| 2066 |
pass
|
| 2067 |
if own and os.path.exists(bp):
|
|
|
|
|
|
|
| 2068 |
try:
|
| 2069 |
req = import_blob(core, bp, device, nocache).create_infer_request()
|
| 2070 |
USED_BLOBS.add(bp)
|
|
@@ -2101,6 +2271,12 @@ class SegmentedModel:
|
|
| 2101 |
f.write(buf.getbuffer())
|
| 2102 |
del buf
|
| 2103 |
os.replace(f"{bpw}.{os.getpid()}.tmp", bpw)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2104 |
req = import_blob(core, bpw, device, {**nocache, "MODEL_PTR": model}).create_infer_request()
|
| 2105 |
USED_BLOBS.add(bpw)
|
| 2106 |
_WEIGHTLESS["ok"] = True
|
|
@@ -2131,6 +2307,10 @@ class SegmentedModel:
|
|
| 2131 |
# child compiles without CACHE_DIR (no second copy in OpenVINO's cache), otherwise it keeps
|
| 2132 |
# CACHE_DIR and the compile below is a cache hit
|
| 2133 |
r = _probe(d, name, nocache if (own or npuw_dir) else cfg, npuw, T, export=bp, ring=ring)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2134 |
if r is True and own and os.path.exists(bp):
|
| 2135 |
try:
|
| 2136 |
req = import_blob(core, bp, device, nocache).create_infer_request()
|
|
@@ -2175,6 +2355,10 @@ class SegmentedModel:
|
|
| 2175 |
# import what was just written and drop the compiled model: the first load then holds no more
|
| 2176 |
# memory than later ones (a compiled model keeps its blob next to the driver's copy)
|
| 2177 |
del cm
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2178 |
req = import_blob(core, bp, device, nocache).create_infer_request()
|
| 2179 |
else:
|
| 2180 |
req = cm.create_infer_request()
|
|
@@ -2559,6 +2743,7 @@ class SegmentedModel:
|
|
| 2559 |
lg = F.step([int(t) for t in ids], want_logits, embeds)
|
| 2560 |
if lg is not False:
|
| 2561 |
return lg
|
|
|
|
| 2562 |
w = self._wired
|
| 2563 |
if self._wire_for is not (self.segs.get(min(self.segs)) if self.segs else None): # (a size dropped / loaded since)
|
| 2564 |
w = self._wire(quiet=True)
|
|
@@ -2587,6 +2772,10 @@ class SegmentedModel:
|
|
| 2587 |
return logits
|
| 2588 |
|
| 2589 |
def _prefill_layers(self, ids, embeds=None, pos=None, want_logits=True, stop=None):
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2590 |
"""A whole prompt, layer by layer: every block goes through segment 0, then every block through segment 1,
|
| 2591 |
... Each layer's state only depends on that layer's earlier blocks, so this is exact, and with experts
|
| 2592 |
streamed from the SSD each layer's experts are read once for the whole prompt instead of once per block.
|
|
|
|
| 311 |
"""[2.0] Every block size the model folder has graphs for: "sizes" (what onw before 2.0 reads) + "prompt_sizes"
|
| 312 |
(larger prompt blocks added to a published model later, see compact.graft_sizes - out of older onw's sight, whose
|
| 313 |
loader cannot read their seg<i>x.bin)."""
|
| 314 |
+
# [3.2] "kit_sizes": the 2-bit (Bonsai) 64-token graphs - out of the sight of onw before 3.2, which would compute
|
| 315 |
+
# them wrong without the kit (npu_compiler #355) and hold the 2-bit weights twice with it
|
| 316 |
+
return sorted(set(meta["sizes"]) | set(meta.get("prompt_sizes") or []) | set(meta.get("kit_sizes") or []))
|
| 317 |
|
| 318 |
|
| 319 |
_CONST_RE = re.compile(rb'<layer id="\d+" name="([^"]+)" type="Const"([^>]*)>(\s*)<data ([^>]*?)offset="(\d+)" size="(\d+)"([^>]*)/>')
|
|
|
|
| 350 |
return model
|
| 351 |
|
| 352 |
|
| 353 |
+
def _own_mapping():
|
| 354 |
+
"""[3.2] Weightless blobs on Windows: the weights are read through our own file mapping (as with NPUW), so that
|
| 355 |
+
release_pages can drop their pages from the working set once the init schedules put them on the NPU - read by
|
| 356 |
+
OpenVINO's own mapping they stayed (Panther Lake, Bonsai 4B: working set 0.9 GB above the private memory).
|
| 357 |
+
ONW_MAP_BINS=0: OpenVINO's mapping as before."""
|
| 358 |
+
return os.name == "nt" and os.environ.get("ONW_MAP_BINS") != "0" and weightless_wanted()
|
| 359 |
+
|
| 360 |
+
|
| 361 |
def read_segment(core, d, name, m, npuw, bins, T, built_T):
|
| 362 |
"""The segment's ov.Model, reshaped to context T. With NPUW every segment of a .bin reads against the same
|
| 363 |
in-memory copy (bins cache), so the weights bank sees one set of constants."""
|
|
|
|
| 368 |
model = _xbin_model(core, d, name, m, bins[b])
|
| 369 |
if npuw:
|
| 370 |
model = _no_bool_constants(model)
|
| 371 |
+
elif npuw or _own_mapping():
|
| 372 |
b = m.get("bin") or name[:-4] + ".bin"
|
| 373 |
if b not in bins:
|
| 374 |
# a file-backed mapping (OpenVINO shares it, see mapped_bin): NPUW's weights bank keeps reading the
|
| 375 |
# constants after compiling, so the buffer lives as long as the model (SegmentedModel._bins), and as
|
| 376 |
# mapped pages it costs no private memory
|
| 377 |
bins[b] = ov.Tensor(mapped_bin(os.path.join(d, b)), shared_memory=True)
|
| 378 |
+
model = core.read_model(open(os.path.join(d, name), encoding="utf8").read(), bins[b])
|
| 379 |
+
if npuw:
|
| 380 |
+
model = _no_bool_constants(model)
|
| 381 |
else:
|
| 382 |
model = core.read_model(os.path.join(d, name), os.path.join(d, m["bin"]) if "bin" in m else None)
|
| 383 |
if T != built_T:
|
| 384 |
model.reshape(reshaped_inputs(model, m["S"], built_T, T))
|
| 385 |
if os.environ.get("ONW_CODES_SCALE") == "const" and codes_const_scale(model): # (opt-in, see there; marked
|
| 386 |
model.set_rt_info("1", "onw_codes_const") # for the compile keys)
|
| 387 |
+
if os.environ.get("ONW_LEFTMM") != "0" and right_const_matmuls(model): # (marked for the compile keys)
|
| 388 |
+
model.set_rt_info("1", "onw_leftmm")
|
| 389 |
return model
|
| 390 |
|
| 391 |
|
| 392 |
+
def right_const_matmuls(model):
|
| 393 |
+
"""[3.2] MatMul(C, X) - a constant 2-D C times a batched X [..., a, b] (Bonsai's Hadamard rotation, ir.hadamard:
|
| 394 |
+
H_a X H_b) - becomes Transpose(MatMul(Transpose(X), C^T)): the NPU compiler unrolls a constant on the left into one
|
| 395 |
+
tiny matmul per batch (512 per 64-token block of Bonsai 4B, 93% of a part's matmuls), which made its compile 3-4x
|
| 396 |
+
slower; on the right it is one matmul. The same values (CPU: equal outputs). Returns how many were rewritten."""
|
| 397 |
+
import openvino.opset13 as ov_ops
|
| 398 |
+
|
| 399 |
+
def const2d(v):
|
| 400 |
+
n, conv = v.get_node(), None
|
| 401 |
+
if n.get_type_name() == "Convert":
|
| 402 |
+
conv, n = n, n.input_value(0).get_node()
|
| 403 |
+
if n.get_type_name() != "Constant" or len(n.get_output_partial_shape(0)) != 2:
|
| 404 |
+
return None
|
| 405 |
+
return n, conv
|
| 406 |
+
k = 0
|
| 407 |
+
for op in list(model.get_ordered_ops()):
|
| 408 |
+
if op.get_type_name() != "MatMul":
|
| 409 |
+
continue
|
| 410 |
+
at = op.get_attributes()
|
| 411 |
+
x = op.input_value(1)
|
| 412 |
+
r = len(x.get_partial_shape())
|
| 413 |
+
got = const2d(op.input_value(0))
|
| 414 |
+
if at.get("transpose_a") or at.get("transpose_b") or got is None or r < 3 or const2d(x) is not None:
|
| 415 |
+
continue
|
| 416 |
+
cn, conv = got
|
| 417 |
+
ct = ov_ops.constant(np.ascontiguousarray(cn.get_data().T))
|
| 418 |
+
if conv is not None:
|
| 419 |
+
ct = ov_ops.convert(ct, conv.get_destination_type())
|
| 420 |
+
perm = ov_ops.constant(np.array(list(range(r - 2)) + [r - 1, r - 2], np.int64))
|
| 421 |
+
y = ov_ops.transpose(ov_ops.matmul(ov_ops.transpose(x, perm), ct, False, False), perm)
|
| 422 |
+
for t in op.output(0).get_target_inputs():
|
| 423 |
+
t.replace_source_output(y.output(0))
|
| 424 |
+
k += 1
|
| 425 |
+
if k:
|
| 426 |
+
model.validate_nodes_and_infer_types()
|
| 427 |
+
return k
|
| 428 |
+
|
| 429 |
+
|
| 430 |
GROUPED_MIN = int(os.environ.get("ONW_GROUPED_MIN", "2")) # [2.0] prompts from this long go the grouped-MoE path
|
| 431 |
GROUPED_RAM_GB = 24 # [2.0] RAM the grouped-MoE prompt path needs
|
| 432 |
|
|
|
|
| 996 |
pass
|
| 997 |
|
| 998 |
|
| 999 |
+
class _NoBank(dict):
|
| 1000 |
+
"""[3.2] A precompile child's stand-in for the expert bank: truthy like a loaded one (the compile routes test it),
|
| 1001 |
+
holding nothing."""
|
| 1002 |
+
def __bool__(self):
|
| 1003 |
+
return True
|
| 1004 |
+
|
| 1005 |
+
|
| 1006 |
class Precompiled(Exception):
|
| 1007 |
"""Raised by a precompile child (ONW_PRECOMPILE_ONLY) once its segments are in the compile cache."""
|
| 1008 |
|
|
|
|
| 1021 |
(Panther Lake, Ornith-1.5-9B NF4: 8.4 instead of 5.2 GB of NPU memory, onw 1.4.2 / 1.4.3 did this by default).
|
| 1022 |
Each child holds about 3.5 GB at its peak."""
|
| 1023 |
env = os.environ.get("ONW_COMPILE_JOBS")
|
| 1024 |
+
if device != "NPU":
|
| 1025 |
return 1
|
| 1026 |
+
if npuw:
|
| 1027 |
+
return max(1, int(env)) if env and not mem_saver() else 1
|
| 1028 |
+
if env:
|
| 1029 |
+
return max(1, int(env))
|
| 1030 |
+
# [3.2] onw's own blobs (no NPUW): the children only compile and write the blobs (export_only, no import - no
|
| 1031 |
+
# weights on the NPU, no copies), so as many as the free memory holds at ~2.5 GB a child (a 2-bit Bonsai part
|
| 1032 |
+
# peaks at ~1.1 GB), at most half the cores, 6; 2 in the memory-saving mode. Panther Lake compiled Bonsai 4B's 26
|
| 1033 |
+
# parts one after the other for 19 minutes on one of its 16 cores. ONW_COMPILE_JOBS=1: one at a time.
|
| 1034 |
+
try:
|
| 1035 |
+
import psutil
|
| 1036 |
+
free = psutil.virtual_memory().available / 2**30
|
| 1037 |
+
except Exception:
|
| 1038 |
+
return 1
|
| 1039 |
+
n = max(1, min(int((free - 3) / 2.5), (os.cpu_count() or 2) // 2, 6))
|
| 1040 |
+
return min(n, 2) if mem_saver() else n
|
| 1041 |
+
|
| 1042 |
+
|
| 1043 |
+
def export_only():
|
| 1044 |
+
"""[3.2] A precompile child of the own-blob route: compile and write the blobs, import nothing (the parent does)."""
|
| 1045 |
+
return bool(os.environ.get("ONW_PRECOMPILE_ONLY")) and os.environ.get("ONW_PRECOMPILE_EXPORT") == "1"
|
| 1046 |
+
|
| 1047 |
+
|
| 1048 |
+
class _Exported:
|
| 1049 |
+
"""What _compile returns in an export-only child: the graph's inputs / outputs, nothing to run."""
|
| 1050 |
+
def __init__(self, model):
|
| 1051 |
+
self.model_inputs, self.model_outputs = model.inputs, model.outputs
|
| 1052 |
|
| 1053 |
|
| 1054 |
def setup_mark(cache, config, tried_key):
|
|
|
|
| 1143 |
config = enable_turbo(core, device, config) # (see turbo(): on unless ONW_TURBO=0)
|
| 1144 |
config = enable_compiler(core, device, config) # [2.0.3] (see npu_compiler())
|
| 1145 |
t0 = time.time()
|
| 1146 |
+
child = bool(os.environ.get("ONW_PRECOMPILE_ONLY"))
|
| 1147 |
+
if os.path.exists(os.path.join(d, "experts.bin")) and child:
|
| 1148 |
+
# [3.2] a precompile child only compiles (it stops before binding anything): no copy of the expert bank in
|
| 1149 |
+
# NPU-visible memory - six children holding Qwen3.6-35B-A3B's bank each hung the NPU driver (Panther Lake)
|
| 1150 |
+
idx = json.load(open(os.path.join(d, "experts.bin.json")))
|
| 1151 |
+
self.bank, self.E, self.K, self.streaming = _NoBank(), idx["E"], idx["K"], False
|
| 1152 |
+
elif os.path.exists(os.path.join(d, "experts.bin")):
|
| 1153 |
self.bank, self.E, self.K = self._expert_bank(core, d, device, sizes)
|
| 1154 |
else: # dense model
|
| 1155 |
self.bank, self.E, self.K, self.streaming = {}, 0, 0, False
|
| 1156 |
sp = os.path.join(d, "shared.bin")
|
| 1157 |
+
# (a precompile child: every shared tensor stays mapped from the file, none copied into NPU-visible memory)
|
| 1158 |
+
self.shared = load_shared(core, sp, device, set() if child else self._shared_inputs(d)) if os.path.exists(sp) else {}
|
| 1159 |
self.embed_rows = self._table("embed")
|
| 1160 |
self.window = self.meta.get("masks", {}).get("sliding_window")
|
| 1161 |
self.host_inputs = {}
|
|
|
|
| 1180 |
# INT4 weights of the plain paged models as well: never separated by the compiler (Granite 4.2 3B INT4 part:
|
| 1181 |
# 63.5 MB weightless against 63.6 MB, multi-row parts 20-35x slower to compile) - INT8 and NF4 are (llm-jp INT8
|
| 1182 |
# part: 2.5 against 216 MB). (NPUW and MoE models as they were checked: gemma-4, Qwen3.6)
|
| 1183 |
+
# [3.2] 2-bit weights weightless by default (onw's kit: compiler fixes; ONW_U2_WEIGHTLESS=0: full blobs)
|
| 1184 |
+
_WEIGHTLESS["skip_model"] = self.meta.get("needs") == "u2" and os.environ.get("ONW_U2_WEIGHTLESS", "1") == "0"
|
| 1185 |
_WEIGHTLESS["full"] = False # (this model: a part with its weights inside, see fused.wanted)
|
| 1186 |
if device == "NPU" and not _WEIGHTLESS.get("told"):
|
| 1187 |
from . import npukit
|
|
|
|
| 1199 |
print(f"[npuw] off: ~{parts} compiled parts (each attention layer cuts a segment) - more than the NPU driver "
|
| 1200 |
f"holds with NPUW (~30); ONW_NPUW=1 forces it", flush=True)
|
| 1201 |
npuw = False
|
| 1202 |
+
if not npuw and not self.bank and int4_weights(d, self.meta) and os.environ.get("ONW_INT4_WEIGHTLESS") != "1":
|
| 1203 |
_WEIGHTLESS["skip_model"] = True # (decided here: NPUW may have been turned off just above)
|
| 1204 |
if npuw:
|
| 1205 |
# experimental: NPUW pulls the weights out of every compiled graph into one weights bank; constants match
|
|
|
|
| 1290 |
and os.path.exists(os.path.join(d, m.get("bin") or n[:-4] + ".bin"))] or [0.0])
|
| 1291 |
npuw_check_runs(cache, graphs, covered, newest)
|
| 1292 |
jobs = compile_jobs(core, device, npuw) if cache and own and not (only or precompile) else 1
|
| 1293 |
+
# [3.2] the own-blob route: its own mark (a load with these graphs went through: all blobs are there)
|
| 1294 |
+
own_mark = loaded_mark(cache, config, tried.key, graphs) if cache and own and not npuw and device == "NPU" \
|
| 1295 |
+
else None
|
| 1296 |
if jobs > 1 and mark and not covered:
|
| 1297 |
self._precompile(jobs, sorted(n for n in self.meta["segments"] if n[:-4] in graphs), todo)
|
| 1298 |
tried = self._ctx["tried"] = _Tried(config, core, device, npuw) # (what the children recorded)
|
| 1299 |
+
elif jobs > 1 and own_mark and not os.path.exists(own_mark):
|
| 1300 |
+
self._precompile(jobs, sorted(n for n in self.meta["segments"] if n[:-4] in graphs), todo,
|
| 1301 |
+
export=True)
|
| 1302 |
+
tried = self._ctx["tried"] = _Tried(config, core, device, npuw)
|
| 1303 |
for attempt in range(2):
|
| 1304 |
k, tc = 0, time.time()
|
| 1305 |
for name, m in sorted(self.meta["segments"].items()):
|
|
|
|
| 1374 |
runs = set(NPUW_SEEN.values())
|
| 1375 |
if mark and not (NPUW_SEEN and (len(runs) > 1 or None in runs)): # (every graph is in the cache, one run)
|
| 1376 |
write_mark(mark, graphs)
|
| 1377 |
+
if own_mark and not dropped: # (every blob is in the store: no precompile next time)
|
| 1378 |
+
write_mark(own_mark, graphs)
|
| 1379 |
# the MTP layer (draft tokens, Qwen3.5 dense): compiled without NPUW, before the paged KV cache is set up
|
| 1380 |
# (its attention layer gets a cache of its own); mtp: wanted (chat.use_mtp)
|
| 1381 |
self.mtp, self.last_h = None, None
|
|
|
|
| 1431 |
# memory-saving mode (ONW_LAZY16=1): block sizes come and go, so the state lives in tensors of its own (else in
|
| 1432 |
# the smallest size's requests)
|
| 1433 |
self.swapping = bool(deferred)
|
| 1434 |
+
# [3.2] the state in tensors of its own as well when the paged graphs may go for the fused ones
|
| 1435 |
+
# (_drop_paged_for_fused): in the dropped requests' buffers the fused graphs wrote into freed memory (Panther
|
| 1436 |
+
# Lake, Bonsai 2 9B: an access violation in the NPU driver at the first prompt block)
|
| 1437 |
+
self._own_state = self.swapping or (self.paged and device == "NPU"
|
| 1438 |
+
and os.environ.get("ONW_FUSED_DROP_PAGED", "1") == "1")
|
| 1439 |
+
mk = host_tensor(core, device) if self._own_state else None
|
| 1440 |
for req, m, names in self.segs[min(self.segs)]:
|
| 1441 |
for n in names:
|
| 1442 |
if n.startswith(("past_k.", "past_v.", "conv.", "ssm.")):
|
|
|
|
| 1506 |
def fused_keep_all(self):
|
| 1507 |
"""[3.0] every fused variant stays loaded (onw's NPU kit, NPU 4000+, not the memory-saving mode)."""
|
| 1508 |
from . import npukit
|
| 1509 |
+
# [3.2] not for 2-bit weights: every variant has its own weight tables (1 bit per weight: Bonsai 2 27B +3.4 GB
|
| 1510 |
+
# a variant) - one variant at a time, swapped as the chat grows
|
| 1511 |
return os.environ.get("ONW_FUSED_KEEP", "1" if npukit.active() and npu_arch(self._ctx["core"]) >= 4000
|
| 1512 |
+
and not mem_saver() and self.meta.get("needs") != "u2" else "0") == "1"
|
| 1513 |
|
| 1514 |
def fused_prepare(self, need, S=None):
|
| 1515 |
"""Make the fused S-token graphs (default: the answer size) covering `need` positions the ones in use for that
|
|
|
|
| 1552 |
return None
|
| 1553 |
F = next((keep[(S, B)] for B in bs if B >= want and (S, B) in keep), None)
|
| 1554 |
if F is None:
|
| 1555 |
+
n = self._fused_jobs_parallel()
|
| 1556 |
+
if n > 1 and not (self._ctx["config"] or {}).get("NPU_USE_NPUW") == "YES":
|
| 1557 |
+
self._fused_children([(S, want)], n) # [3.2] (its graphs at the same time; the load imports them)
|
| 1558 |
with self._mutex:
|
| 1559 |
try:
|
| 1560 |
F = keep[(S, want)] = _f.FusedDecode(self, S, want)
|
|
|
|
| 1563 |
self._fused_sizes = [x for x in sizes if x != S]
|
| 1564 |
return None
|
| 1565 |
self._fused[S] = F
|
| 1566 |
+
self._drop_paged_for_fused(S)
|
| 1567 |
self.S = max([*self.segs, *self._fused])
|
| 1568 |
self._fused_background(caps)
|
| 1569 |
return F
|
|
|
|
| 1579 |
return None
|
| 1580 |
if F is not None and F.B >= want:
|
| 1581 |
return F
|
| 1582 |
+
n = self._fused_jobs_parallel() # [3.2] (its graphs compiled at the same time first)
|
| 1583 |
+
if n > 1 and not (self._ctx["config"] or {}).get("NPU_USE_NPUW") == "YES":
|
| 1584 |
+
self._fused_children([(S, want)], n)
|
| 1585 |
F = None # [2.1.0] (this reference kept the old variant alive while the
|
| 1586 |
with self._mutex: # next one loaded: peak working set +3.3 GB, NPU 3720 Granite 3B)
|
| 1587 |
self._fused.pop(S, None) # (out first: never two copies of the weights)
|
|
|
|
| 1605 |
self._fused_background(caps)
|
| 1606 |
return self._fused[S]
|
| 1607 |
|
| 1608 |
+
def _drop_paged_for_fused(self, S, before=False):
|
| 1609 |
+
"""[3.2] Memory first: the fused S-token graphs run this size for every position they cover, so the paged
|
| 1610 |
+
S-token graphs go (their weight tables are as large as the fused ones': Bonsai 4B +1.3 GB on the NPU with both;
|
| 1611 |
+
the weights themselves are shared). They come back when a block needs them - images (explicit positions),
|
| 1612 |
+
past the fused variants (_paged_back). Not with MTP (its verification blocks use them). ONW_FUSED_DROP_PAGED=0:
|
| 1613 |
+
kept as before."""
|
| 1614 |
+
if (os.environ.get("ONW_FUSED_DROP_PAGED", "1") != "1" or S not in self.segs or self.mtp is not None
|
| 1615 |
+
or not self._own_state):
|
| 1616 |
+
return
|
| 1617 |
+
only = os.environ.get("ONW_FUSED_DROP_SIZES") # (test: only these sizes, e.g. "64")
|
| 1618 |
+
if only and str(S) not in only.split(","):
|
| 1619 |
+
return
|
| 1620 |
+
if before and not self._fused_ready(S): # (no fused blobs yet: dropped once they are prepared)
|
| 1621 |
+
return
|
| 1622 |
+
with self._mutex:
|
| 1623 |
+
self.drop_size(S, force=True)
|
| 1624 |
+
self._wired = self._wire_for = None
|
| 1625 |
+
self.__dict__.setdefault("_paged_dropped", set()).add(S)
|
| 1626 |
+
self._trim()
|
| 1627 |
+
print(f"[fused] the paged {S}-token graphs out (the fused ones run this size; back when a block needs them)",
|
| 1628 |
+
flush=True)
|
| 1629 |
+
|
| 1630 |
+
def _fused_ready(self, S):
|
| 1631 |
+
"""[3.2] Whether the paged S-token graphs may go before the fused ones are prepared: always - the fused graphs
|
| 1632 |
+
compile in child processes (or here, from the files) without them, and if they fail the paged ones come back
|
| 1633 |
+
when a block needs them (_paged_back)."""
|
| 1634 |
+
return S in (self._fused_sizes or [])
|
| 1635 |
+
|
| 1636 |
+
def _paged_back(self):
|
| 1637 |
+
"""[3.2] The paged graphs dropped for the fused ones, loaded again (a block the fused graphs cannot run)."""
|
| 1638 |
+
dropped = self.__dict__.get("_paged_dropped")
|
| 1639 |
+
if not dropped:
|
| 1640 |
+
return
|
| 1641 |
+
for S in sorted(dropped):
|
| 1642 |
+
self.swap_to(S)
|
| 1643 |
+
dropped.clear()
|
| 1644 |
+
self._wired = self._wire_for = None
|
| 1645 |
+
|
| 1646 |
def _fused_background(self, caps):
|
| 1647 |
npuw_ = (self._ctx["config"] or {}).get("NPU_USE_NPUW") == "YES" # (NPUW: compiled when needed, no background)
|
| 1648 |
if os.environ.get("ONW_FUSED_BG", "1") != "0" and not self._fused_bg and not npuw_:
|
|
|
|
| 1662 |
env = os.environ.get("ONW_FUSED_JOBS")
|
| 1663 |
if env:
|
| 1664 |
return max(1, int(env))
|
| 1665 |
+
# [3.2] on by default (the children only compile and write blobs; ONW_FUSED_JOBS_AUTO=0: one at a time here);
|
| 1666 |
+
# 2 at most in the memory-saving mode
|
| 1667 |
+
if self._ctx["device"] != "NPU" or os.environ.get("ONW_FUSED_JOBS_AUTO") == "0":
|
| 1668 |
+
return 1
|
| 1669 |
try:
|
| 1670 |
import psutil
|
| 1671 |
free = psutil.virtual_memory().available / 2**30
|
| 1672 |
except Exception:
|
| 1673 |
return 1
|
| 1674 |
+
n = max(1, min(int((free - 4) / 3), (os.cpu_count() or 2) // 2, 6))
|
| 1675 |
+
return min(n, 2) if mem_saver() else n
|
| 1676 |
|
| 1677 |
def _fused_precompile(self, jobs):
|
| 1678 |
from . import fused as _f
|
|
|
|
| 1702 |
cfg = {k: v for k, v in (c["config"] or {}).items()}
|
| 1703 |
kt = self.kv.kt.get_type_name()
|
| 1704 |
env = dict(os.environ, ONW_WS_NO_CONCAT=os.environ.get("ONW_WS_NO_CONCAT", "1"))
|
| 1705 |
+
# [3.2] one child per graph (segment) of a variant, the longest first: a variant's graphs compile at the same
|
| 1706 |
+
# time (Panther Lake, Bonsai 4B: the 64-token 4K variant's 5 graphs took 590 s one after another)
|
| 1707 |
+
segs = {S_: [n_ for n_, sm in sorted(self.meta["segments"].items()) if sm["S"] == S_] for S_ in {j[0] for j in jobs}}
|
| 1708 |
+
todo = [(S, B, n_) for S, B in sorted(jobs, key=lambda j: (j[1], j[0])) for n_ in segs[S]]
|
| 1709 |
running, ok = [], True
|
| 1710 |
while todo or running:
|
| 1711 |
for p in [p for p in running if p.poll() is not None]:
|
|
|
|
| 1714 |
if not ok:
|
| 1715 |
break
|
| 1716 |
if todo and len(running) < n and (not running or self._fused_jobs_parallel() > len(running)):
|
| 1717 |
+
S, B, seg = todo.pop(0)
|
| 1718 |
arg = json.dumps([c["d"], c["device"], cfg, c["tried"].key, c["built_T"], self.kv.capacity, kt, S, B],
|
| 1719 |
default=str)
|
| 1720 |
flags = 0x00004000 if os.name == "nt" else 0 # (BELOW_NORMAL_PRIORITY_CLASS: answers go first)
|
| 1721 |
+
running.append(subprocess.Popen([sys.executable, "-m", "onw.fusedjob", arg], env={**env, "ONW_FUSED_SEG": seg},
|
| 1722 |
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
|
| 1723 |
creationflags=flags))
|
| 1724 |
continue
|
|
|
|
| 1727 |
p.wait()
|
| 1728 |
if not ok:
|
| 1729 |
print("[fused] a background compile process failed: the rest one after another here", flush=True)
|
| 1730 |
+
for S, B in sorted({(j[0], j[1]) for j in todo}):
|
| 1731 |
try:
|
| 1732 |
from . import fused as _f
|
| 1733 |
_f.FusedDecode(self, S, B, compile_only=True, log=lambda *a, **k: None)
|
|
|
|
| 1892 |
lg[np.isin(self.mtp["vocab"], self.banned)] = -np.inf
|
| 1893 |
return int(self.mtp["vocab"][int(lg.argmax())])
|
| 1894 |
|
| 1895 |
+
def _precompile(self, jobs, names, todo, export=False):
|
| 1896 |
"""Compile these segments into the cache in child processes, `jobs` at a time (see compile_jobs); the load
|
| 1897 |
+
then imports them. A child that fails leaves its segment to the load (compiled there, as without this).
|
| 1898 |
+
export: the children only write onw's blobs (export_only), the biggest segments first."""
|
| 1899 |
import subprocess, sys
|
| 1900 |
kw = {"creationflags": subprocess.CREATE_NO_WINDOW} if os.name == "nt" else {}
|
| 1901 |
env = {**os.environ, "PYTHONUTF8": "1", "ONW_NPUW_SESSION": NPUW_SESSION}
|
| 1902 |
+
if export:
|
| 1903 |
+
env["ONW_PRECOMPILE_EXPORT"] = "1"
|
| 1904 |
+
d = self._ctx["d"] # (the longest compiles start first: less waiting at the end)
|
| 1905 |
+
names = sorted(names, key=lambda n: -self.meta["segments"][n]["S"] * os.path.getsize(os.path.join(d, n)))
|
| 1906 |
args = json.dumps(self._args)
|
| 1907 |
print(f"[compile] {len(names)} segments, {jobs} processes at a time", flush=True)
|
| 1908 |
t0, waiting, running, done = time.time(), list(names), [], 0
|
|
|
|
| 1976 |
the expert bank; note which carried values each segment's successors still read."""
|
| 1977 |
for req, m, names in lst:
|
| 1978 |
for n in names:
|
| 1979 |
+
if n in self._owner and (self._own_state or S != min(self.segs, default=S)):
|
| 1980 |
req.set_tensor(n, self._owner[n])
|
| 1981 |
elif n.startswith("shared.") and n[7:] in self.shared:
|
| 1982 |
req.set_tensor(n, self.shared[n[7:]])
|
|
|
|
| 2177 |
[xml, size, S, "paged-1", part, ring, _signature(model), tried.key]
|
| 2178 |
if part is None and model.has_rt_info("onw_codes_const"): # (a part's signature tells the forms apart)
|
| 2179 |
src.append("codes-const")
|
| 2180 |
+
if part is None and model.has_rt_info("onw_leftmm"):
|
| 2181 |
+
src.append("leftmm")
|
| 2182 |
# DeltaNet parts of the 4- and 64-token blocks (Qwen3.5): REP folds the repeated rows / chunks inside a layer
|
| 2183 |
# into graphs of undecided shape, which no NPU compiler takes (2-6 s lost per part, an empty cache folder
|
| 2184 |
# left); written so that REP takes them, they ran 6-12% slower than as one graph (Panther Lake) - so NONE first
|
|
|
|
| 2216 |
except OSError:
|
| 2217 |
pass
|
| 2218 |
if bpw and os.path.exists(bpw) and weightless_wanted():
|
| 2219 |
+
if export_only(): # [3.2] (a precompile child: already there)
|
| 2220 |
+
return _Exported(model)
|
| 2221 |
try:
|
| 2222 |
req = import_blob(core, bpw, device, {**nocache, "MODEL_PTR": model}).create_infer_request()
|
| 2223 |
USED_BLOBS.add(bpw)
|
|
|
|
| 2233 |
except OSError:
|
| 2234 |
pass
|
| 2235 |
if own and os.path.exists(bp):
|
| 2236 |
+
if export_only():
|
| 2237 |
+
return _Exported(model)
|
| 2238 |
try:
|
| 2239 |
req = import_blob(core, bp, device, nocache).create_infer_request()
|
| 2240 |
USED_BLOBS.add(bp)
|
|
|
|
| 2271 |
f.write(buf.getbuffer())
|
| 2272 |
del buf
|
| 2273 |
os.replace(f"{bpw}.{os.getpid()}.tmp", bpw)
|
| 2274 |
+
if export_only(): # [3.2] the parent imports it
|
| 2275 |
+
_WEIGHTLESS["ok"] = True
|
| 2276 |
+
tried.set(gname, how)
|
| 2277 |
+
tried.save()
|
| 2278 |
+
print(f"[compile] {gname}: {how} (weightless, exported)", flush=True)
|
| 2279 |
+
return _Exported(model)
|
| 2280 |
req = import_blob(core, bpw, device, {**nocache, "MODEL_PTR": model}).create_infer_request()
|
| 2281 |
USED_BLOBS.add(bpw)
|
| 2282 |
_WEIGHTLESS["ok"] = True
|
|
|
|
| 2307 |
# child compiles without CACHE_DIR (no second copy in OpenVINO's cache), otherwise it keeps
|
| 2308 |
# CACHE_DIR and the compile below is a cache hit
|
| 2309 |
r = _probe(d, name, nocache if (own or npuw_dir) else cfg, npuw, T, export=bp, ring=ring)
|
| 2310 |
+
if r is True and own and os.path.exists(bp) and export_only():
|
| 2311 |
+
tried.set(gname, how)
|
| 2312 |
+
tried.save()
|
| 2313 |
+
return _Exported(model)
|
| 2314 |
if r is True and own and os.path.exists(bp):
|
| 2315 |
try:
|
| 2316 |
req = import_blob(core, bp, device, nocache).create_infer_request()
|
|
|
|
| 2355 |
# import what was just written and drop the compiled model: the first load then holds no more
|
| 2356 |
# memory than later ones (a compiled model keeps its blob next to the driver's copy)
|
| 2357 |
del cm
|
| 2358 |
+
if export_only(): # [3.2] the parent imports it
|
| 2359 |
+
tried.set(gname, how)
|
| 2360 |
+
tried.save()
|
| 2361 |
+
return _Exported(model)
|
| 2362 |
req = import_blob(core, bp, device, nocache).create_infer_request()
|
| 2363 |
else:
|
| 2364 |
req = cm.create_infer_request()
|
|
|
|
| 2743 |
lg = F.step([int(t) for t in ids], want_logits, embeds)
|
| 2744 |
if lg is not False:
|
| 2745 |
return lg
|
| 2746 |
+
self._paged_back() # [3.2] (dropped for the fused graphs: needed here)
|
| 2747 |
w = self._wired
|
| 2748 |
if self._wire_for is not (self.segs.get(min(self.segs)) if self.segs else None): # (a size dropped / loaded since)
|
| 2749 |
w = self._wire(quiet=True)
|
|
|
|
| 2772 |
return logits
|
| 2773 |
|
| 2774 |
def _prefill_layers(self, ids, embeds=None, pos=None, want_logits=True, stop=None):
|
| 2775 |
+
self._paged_back() # [3.2] (dropped for the fused graphs: needed here)
|
| 2776 |
+
return self._prefill_layers2(ids, embeds, pos, want_logits, stop)
|
| 2777 |
+
|
| 2778 |
+
def _prefill_layers2(self, ids, embeds=None, pos=None, want_logits=True, stop=None):
|
| 2779 |
"""A whole prompt, layer by layer: every block goes through segment 0, then every block through segment 1,
|
| 2780 |
... Each layer's state only depends on that layer's earlier blocks, so this is exact, and with experts
|
| 2781 |
streamed from the SSD each layer's experts are read once for the whole prompt instead of once per block.
|
onw/visionjob.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""[3.2] A background child that compiles the vision encoder into onw's blob store at a first load (chat.export_vision_blob),
|
| 2 |
+
so the first image only imports it. usage (internal): python -m onw.visionjob '[MODEL_DIR, DEVICE, CONFIG]'"""
|
| 3 |
+
import json
|
| 4 |
+
import sys
|
| 5 |
+
|
| 6 |
+
from . import npukit
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def main():
|
| 10 |
+
d, device, cfg = json.loads(sys.argv[1])
|
| 11 |
+
from .chat import export_vision_blob
|
| 12 |
+
from .runtime import enable_compiler
|
| 13 |
+
core = npukit.core()
|
| 14 |
+
enable_compiler(core, device, {}, quiet=True)
|
| 15 |
+
export_vision_blob(core, d, device, cfg)
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
if __name__ == "__main__":
|
| 19 |
+
main()
|
onw/wired.py
CHANGED
|
@@ -7,10 +7,12 @@ SegmentedModel._step (NPU 3720, Qwen3.6-35B-A3B: 146.5 -> 139.5 ms a token). ONW
|
|
| 7 |
[2.0.4] Dense models too (paged attention), on their decode size: the 1-token graphs, or the 2-token ones that answer on
|
| 8 |
NPU 4000+ since 2.0.3 (one real row, the second masked by token_valid). Granite 4.2 8B on Panther Lake: the stock path
|
| 9 |
spent ~40 of 167 ms a token on the host."""
|
| 10 |
-
import os
|
| 11 |
import numpy as np
|
| 12 |
from .store import host_tensor
|
| 13 |
|
|
|
|
|
|
|
| 14 |
COMMON = ("inputs_embeds", "rope_cos", "rope_sin", "position_ids", "token_valid", "mask")
|
| 15 |
|
| 16 |
|
|
@@ -80,6 +82,11 @@ class Wired:
|
|
| 80 |
m._tok += 1
|
| 81 |
m._last = {"start": m.cur, "cx": {}, "rows": {}, "prev": {} if n > 1 else None, "pp": None}
|
| 82 |
cur, later, logits = m.cur, None, None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
for pi, st in enumerate(self.plan):
|
| 84 |
req, meta = st["req"], st["meta"]
|
| 85 |
if st["head"] and not want_logits:
|
|
@@ -101,11 +108,16 @@ class Wired:
|
|
| 101 |
bufs, c = m._pp[k]
|
| 102 |
req.set_tensor(k, bufs[c])
|
| 103 |
req.set_tensor("ssm_new." + k[4:], bufs[1 - c])
|
|
|
|
|
|
|
| 104 |
req.start_async()
|
| 105 |
if later is not None:
|
| 106 |
later()
|
| 107 |
later = None
|
| 108 |
req.wait()
|
|
|
|
|
|
|
|
|
|
| 109 |
L = st["kv"]
|
| 110 |
if L is not None: # paged attention: rows into the cache, attention on the NPU
|
| 111 |
if st["kvw"]: # (a layer sharing another's cache only reads it)
|
|
@@ -113,6 +125,8 @@ class Wired:
|
|
| 113 |
q = req.get_tensor(f"q.{L}").data
|
| 114 |
self.parts[pi + 1][0].get_tensor(f"carry.attn.{L}").data[:] = m.kv.attend(
|
| 115 |
L, q[0], cur, S, n, sink=m._sink.get(L), rows_out=True)
|
|
|
|
|
|
|
| 116 |
|
| 117 |
def book(st=st, req=req):
|
| 118 |
for o in st["cx"]:
|
|
@@ -135,6 +149,16 @@ class Wired:
|
|
| 135 |
if later is not None:
|
| 136 |
later()
|
| 137 |
m.cur += n
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 138 |
if logits is not None:
|
| 139 |
if not np.isfinite(logits).all():
|
| 140 |
raise FloatingPointError(f"the {S}-token graph returned NaN/inf logits")
|
|
|
|
| 7 |
[2.0.4] Dense models too (paged attention), on their decode size: the 1-token graphs, or the 2-token ones that answer on
|
| 8 |
NPU 4000+ since 2.0.3 (one real row, the second masked by token_valid). Granite 4.2 8B on Panther Lake: the stock path
|
| 9 |
spent ~40 of 167 ms a token on the host."""
|
| 10 |
+
import os, time
|
| 11 |
import numpy as np
|
| 12 |
from .store import host_tensor
|
| 13 |
|
| 14 |
+
PROFILE = os.environ.get("ONW_PROFILE") == "1" # [3.2] per-part NPU / attention / host times, printed every 64 tokens
|
| 15 |
+
|
| 16 |
COMMON = ("inputs_embeds", "rope_cos", "rope_sin", "position_ids", "token_valid", "mask")
|
| 17 |
|
| 18 |
|
|
|
|
| 82 |
m._tok += 1
|
| 83 |
m._last = {"start": m.cur, "cx": {}, "rows": {}, "prev": {} if n > 1 else None, "pp": None}
|
| 84 |
cur, later, logits = m.cur, None, None
|
| 85 |
+
if PROFILE:
|
| 86 |
+
t_step = time.perf_counter()
|
| 87 |
+
prof = getattr(self, "_prof", None)
|
| 88 |
+
if prof is None:
|
| 89 |
+
prof = self._prof = {"n": 0, "npu": [0.0] * len(self.plan), "attn": 0.0, "step": 0.0}
|
| 90 |
for pi, st in enumerate(self.plan):
|
| 91 |
req, meta = st["req"], st["meta"]
|
| 92 |
if st["head"] and not want_logits:
|
|
|
|
| 108 |
bufs, c = m._pp[k]
|
| 109 |
req.set_tensor(k, bufs[c])
|
| 110 |
req.set_tensor("ssm_new." + k[4:], bufs[1 - c])
|
| 111 |
+
if PROFILE:
|
| 112 |
+
t0 = time.perf_counter()
|
| 113 |
req.start_async()
|
| 114 |
if later is not None:
|
| 115 |
later()
|
| 116 |
later = None
|
| 117 |
req.wait()
|
| 118 |
+
if PROFILE:
|
| 119 |
+
t1 = time.perf_counter()
|
| 120 |
+
prof["npu"][pi] += t1 - t0
|
| 121 |
L = st["kv"]
|
| 122 |
if L is not None: # paged attention: rows into the cache, attention on the NPU
|
| 123 |
if st["kvw"]: # (a layer sharing another's cache only reads it)
|
|
|
|
| 125 |
q = req.get_tensor(f"q.{L}").data
|
| 126 |
self.parts[pi + 1][0].get_tensor(f"carry.attn.{L}").data[:] = m.kv.attend(
|
| 127 |
L, q[0], cur, S, n, sink=m._sink.get(L), rows_out=True)
|
| 128 |
+
if PROFILE:
|
| 129 |
+
prof["attn"] += time.perf_counter() - t1
|
| 130 |
|
| 131 |
def book(st=st, req=req):
|
| 132 |
for o in st["cx"]:
|
|
|
|
| 149 |
if later is not None:
|
| 150 |
later()
|
| 151 |
m.cur += n
|
| 152 |
+
if PROFILE:
|
| 153 |
+
prof["step"] += time.perf_counter() - t_step
|
| 154 |
+
prof["n"] += 1
|
| 155 |
+
if prof["n"] % 64 == 0:
|
| 156 |
+
k = prof["n"]
|
| 157 |
+
npu = sum(prof["npu"])
|
| 158 |
+
print(f"[profile] {k} tokens: {prof['step'] / k * 1e3:.2f} ms a token = parts on the NPU "
|
| 159 |
+
f"{npu / k * 1e3:.2f} + attention {prof['attn'] / k * 1e3:.2f} + host "
|
| 160 |
+
f"{(prof['step'] - npu - prof['attn']) / k * 1e3:.2f}; parts: "
|
| 161 |
+
+ " ".join(f"{v / k * 1e3:.2f}" for v in prof["npu"]), flush=True)
|
| 162 |
if logits is not None:
|
| 163 |
if not np.isfinite(logits).all():
|
| 164 |
raise FloatingPointError(f"the {S}-token graph returned NaN/inf logits")
|
pyproject.toml
CHANGED
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
| 4 |
|
| 5 |
[project]
|
| 6 |
name = "onw"
|
| 7 |
-
version = "3.
|
| 8 |
description = "Ore no NPU ga konna ni ugoku wake nai - LLMs entirely on the Intel NPU"
|
| 9 |
readme = "README.md"
|
| 10 |
license = {text = "Apache-2.0"}
|
|
|
|
| 4 |
|
| 5 |
[project]
|
| 6 |
name = "onw"
|
| 7 |
+
version = "3.2.0"
|
| 8 |
description = "Ore no NPU ga konna ni ugoku wake nai - LLMs entirely on the Intel NPU"
|
| 9 |
readme = "README.md"
|
| 10 |
license = {text = "Apache-2.0"}
|