ryugyosoft commited on
Commit
0c5f488
·
verified ·
1 Parent(s): 67fe962

onw 0.4.1: Japanese README (README.md) + English (README_en.md); setup/start call find_python.bat by path

Browse files
Files changed (6) hide show
  1. README.md +152 -145
  2. README_en.md +136 -0
  3. onw/__init__.py +1 -1
  4. pyproject.toml +1 -1
  5. setup.bat +44 -44
  6. start.bat +52 -52
README.md CHANGED
@@ -1,145 +1,152 @@
1
- ---
2
- license: apache-2.0
3
- library_name: openvino
4
- tags:
5
- - openvino
6
- - npu
7
- - intel
8
- - intel-npu
9
- - moe
10
- - inference-engine
11
- ---
12
-
13
- # onw — 俺のNPUがこんなに動くわけない
14
-
15
- *(Ore no NPU ga konna ni ugoku wake nai — "There's no way my NPU runs this well")*
16
-
17
- A small engine that runs LLMs — including MoE and hybrid DeltaNet models, with image input — **entirely on the
18
- Intel NPU**, with an OpenAI-compatible server and a browser chat UI. Engine and models are separate downloads,
19
- like llama.cpp + a GGUF: one engine, one command, any converted model.
20
-
21
- ```bash
22
- hf download ryugyosoft/onw --local-dir onw && cd onw
23
- setup.bat # Windows: resident task-tray app (recommended)
24
- bash setup.sh # Ubuntu: resident top-bar app
25
- start.bat # or just run the server once (default model: LFM2-8B-A1B)
26
- bash start.sh ryugyosoft/Qwen3.6-35B-A3B-onw # Ubuntu, another model
27
- ```
28
-
29
- `setup` finds Python 3.10+ (on Windows it offers to install Python 3.12 with winget when there is none),
30
- installs the engine and the `onw` command into `.venv`, adds **onw** to the Start menu / application list and
31
- starts a small resident app (task tray on Windows, top-bar indicator on Ubuntu). No model is downloaded yet:
32
- the **onw window** opens on its *Models* tab - pick a model (sizes, memory needs and what fits this PC are shown)
33
- and press *Download*. Then the *Server* tab starts it; the first start of a model compiles it for the NPU, later
34
- starts take ~20 s. The *Settings* tab has port, LAN access, API key, context length, device, prompt lookup
35
- decoding, 64-token blocks, start at login and the models folder.
36
-
37
- **The server is an OpenAI-compatible API**: point other apps at `http://localhost:8000/v1` with the model name
38
- shown in the window (`GET /` describes the server, `/health` answers once the model is loaded, CORS is open,
39
- `--api-key` / the settings' API key protects `/v1`). For trying things out it also serves a chat UI at `/chat` and
40
- a check page at `/check` that runs a text, prompt-lookup, two-turn and image test and shows the speeds; both open
41
- from the tray menu and the window. Without an NPU driver everything runs on the CPU.
42
-
43
- ## The `onw` command
44
-
45
- ```bash
46
- onw serve MODEL [--port 8000] [--host 0.0.0.0] [--api-key KEY] [--context 4096] [--device NPU|CPU] [--no-pld] [--open]
47
- onw chat MODEL [--think] [--context 4096] # chat in the terminal
48
- onw tray # the resident app (what setup starts)
49
- onw window [--tab models] # model manager, server control, settings
50
- onw convert HF_DIR OUT_DIR [--compact] [--prune SAL.json --keep 0.5] [--graphs-only]
51
- onw list # supported architectures
52
- ```
53
-
54
- What needs what:
55
-
56
- | setting | how | recompile? |
57
- |---|---|---|
58
- | temperature, top_p, top_k, repetition / presence / frequency penalty, max_tokens, stop, seed, thinking | per request (API or chat UI) | no |
59
- | port, address, model, device, prompt lookup on/off | server option / tray settings, restart (~20 s) | no |
60
- | **context length** (`--context`) | server option / tray settings | **once per length** (input shapes change; the model files stay the same) |
61
- | 64-token prompt blocks (Gemma) | `ONW_S64=1/0` / tray setting | once, the first time |
62
- | block sizes, image size, quantization | `onw convert` | yes (new conversion) |
63
-
64
- A longer context costs memory and time on every token (the static KV cache is attended in full), so pick what you
65
- need rather than the model's maximum.
66
-
67
- `MODEL` is an onw model folder or a Hugging Face repo id (downloaded into `./models`, resumable). The command is in
68
- `.venv` after the first `start` (`.venv\Scripts\onw` on Windows, `source .venv/bin/activate` on Linux), or
69
- install it anywhere with `pip install --pre -e . --extra-index-url https://storage.openvinotoolkit.org/simple/wheels/nightly`.
70
-
71
- Git LFS guard: a model repo cloned with `git` but without Git LFS contains ~130-byte pointer files instead of the
72
- weights; `onw` detects that and stops with instructions. Prefer `hf download` (or just pass the repo id).
73
-
74
- ## Models
75
-
76
- | onw model | arch | input | NPU 3720 decode | download | memory in use |
77
- |---|---|---|---|---|---|
78
- | [ryugyosoft/LFM2-8B-A1B-onw](https://huggingface.co/ryugyosoft/LFM2-8B-A1B-onw) | `lfm2_moe` (short conv + GQA + MoE 32x top-4) | text | 16-17 tok/s (29-43 with prompt lookup on code / summaries) | 4.0 GB | ~6 GB |
79
- | [ryugyosoft/gemma-4-E4B-it-onw](https://huggingface.co/ryugyosoft/gemma-4-E4B-it-onw) | `gemma4` (imported from the validated Gemma NPU graphs) | text + image | 7.2 tok/s | 4.3 GB | ~15 GB |
80
- | [ryugyosoft/Qwen3.5-9B-onw](https://huggingface.co/ryugyosoft/Qwen3.5-9B-onw) | `qwen3_5` (Gated DeltaNet + gated attention, dense) | text + image | 4.1 tok/s | 5.3 GB | ~20 GB |
81
- | [ryugyosoft/Qwen3.6-REAP-18B-A3B-onw](https://huggingface.co/ryugyosoft/Qwen3.6-REAP-18B-A3B-onw) | `qwen3_5_moe`, 128 of 256 experts kept (REAP) | text + image | 6.8 tok/s | 9.6 GB | ~15 GB |
82
- | [ryugyosoft/Qwen3.6-35B-A3B-onw](https://huggingface.co/ryugyosoft/Qwen3.6-35B-A3B-onw) | `qwen3_5_moe` (Gated DeltaNet + gated attention + MoE 256x top-8 + shared expert) | text + image | 6.8 tok/s | 17 GB | ~23 GB |
83
-
84
- Memory is the process working set on the test machine after loading (includes memory-mapped weight files the
85
- OS can page out).
86
-
87
- Measured with an image chat / text prompts on Core Ultra 9 285HX; prompt lookup decoding is used automatically
88
- where a 16-token verification block is cheap enough (LFM2, Qwen3.5-9B, Gemma), and every model keeps the
89
- conversation state between turns (a follow-up with an image re-processes only the new message: ~2 s).
90
-
91
- ## Smaller models (onw convert --compact / --prune)
92
-
93
- - `--compact`: the 1-token and 16/64-token graphs of a segment share one weights file (download / disk);
94
- host-only lookup tables (token embeddings not tied to the LM head, Gemma's per-layer embeddings) are INT4 with a
95
- scale per 32 values; the Qwen vision tower is INT8. These also shrink the memory in use, except the shared
96
- weights file: the NPU keeps one compiled copy per block size.
97
- - `--prune SAL.json --keep 0.5` (MoE): REAP expert pruning. `python -m onw.prune calibrate HF_DIR SAL.json`
98
- measures each expert's saliency (mean router weight x output norm over chat data in Japanese, English and code),
99
- `python -m onw.prune eval` reports the perplexity for several keep ratios. Qwen3.6-35B-A3B keeps 128 of 256
100
- experts at +8.9% perplexity (published as Qwen3.6-REAP-18B-A3B-onw); LFM2 with only 32 experts loses +35% at
101
- 50%, so it is not pruned.
102
-
103
- ## How it runs a MoE on an NPU
104
-
105
- NPUs need static graphs, and a sparse MoE picks different experts per token. Measured on NPU 3720 (Qwen3.6
106
- sizes, per layer, one token): computing every expert 45 ms; selecting experts inside the graph with Gather does
107
- not compile for INT4 constants and is 18-135 ms for INT8 / packed forms (the NPU moves whole tables);
108
- **passing the chosen experts as runtime inputs: 1.7 ms**, at the NPU's memory bandwidth. So:
109
-
110
- - the network is cut into **segments right after every MoE router**; the router's top-k goes to the host, which
111
- binds the selected experts' weight buffers into the next segment (`set_tensor`, zero copy: all experts live in
112
- NPU-visible host memory, `experts.bin`);
113
- - experts are **channel-wise INT4 packed as one tensor per expert** — the only weights-as-inputs form the NPU runs
114
- at full speed (group-wise scales on runtime weights fall back to slow element-wise work, 20x slower);
115
- - everything else is group-128 INT4 constants inside the segments; the LM head is a shared INT8 input in its own
116
- segment (one copy for all block sizes, skipped for prompt blocks that need no logits); token embeddings and
117
- Gemma's 2.7 GB per-layer embeddings are looked up on the host (a gather from a runtime input makes the NPU move
118
- the whole table);
119
- - prompt blocks bind the union of the block's experts; decoding binds the token's top-k.
120
-
121
- ## Engine pieces
122
-
123
- | file | what |
124
- |---|---|
125
- | `onw/ir.py` | graph blocks: quantized linears, RMSNorm (with an fp16-safe prescale), RoPE / partial RoPE, attention over a host KV cache, expert slots, Gated DeltaNet (1-token matrix form; chunkwise-parallel form with an exact block-doubling inverse) |
126
- | `onw/runtime.py` | runs the segments; state by naming convention (KV rows, conv windows, recurrent states), expert binding, MRoPE, sliding-window masks, host table lookups, exact truncation for speculative verification |
127
- | `onw/chat.py` | chat loop: images (Qwen / Gemma), sampling, stop strings, prompt lookup decoding (exact rewind, or lazy redo for recurrent models, switched on only when it pays), prefix reuse between turns |
128
- | `onw/models/` | one builder per architecture: `lfm2_moe`, `qwen3_5_moe` (dense and MoE), `qwen_vision`, `gemma4` |
129
- | `onw/hub.py`, `onw/server.py`, `onw/web/` | model download + LFS guard, OpenAI-compatible server, chat UI (`/chat`), check page (`/check`) |
130
- | `onw/app.py`, `onw/tray.py`, `setup.*`, `find_python.bat` | onw window (models / server / settings), tray icon, installers (Python detection + winget) |
131
- | `check_ref.py`, `check_vision.py`, `test_chat.py`, `test_image.py`, `probe_segments.py`, `prof_*.py` | vs the HF model, generation / image / multi-turn checks, NPU-vs-CPU per segment, timing |
132
-
133
- ## Notes and limitations
134
-
135
- - Static 1024-token context. Qwen images are resized to 512x512 (256 tokens); Gemma images keep their aspect
136
- ratio (up to 280 tokens).
137
- - Experts are channel-wise INT4 (round-to-nearest): first-token logits within ~33% of bf16 with the same top-1 on
138
- our checks; answers are fluent, word choices diverge from the bf16 model after a few tokens.
139
- - Prompt blocks above 16 tokens (Gemma's 64-token block) are loaded only with >= 24 GB RAM (`ONW_S64=1/0`).
140
- - Needs a pre-release OpenVINO (2026.5 nightly). Tested on NPU 3720 (Windows 11); CPU fallback on Ubuntu 22.04
141
- (WSL). NPU 4000 (Lunar Lake) untested with this engine.
142
-
143
- ## License
144
-
145
- Code: Apache 2.0. Converted models carry their base model's license (see each model repo).
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ library_name: openvino
4
+ language:
5
+ - ja
6
+ - en
7
+ tags:
8
+ - openvino
9
+ - npu
10
+ - intel
11
+ - intel-npu
12
+ - moe
13
+ - inference-engine
14
+ ---
15
+
16
+ # onw — 俺のNPUがこんなに動くわけない
17
+
18
+ **日本語** | [English](README_en.md)
19
+
20
+ LLM を **Intel NPU だけで** 動かす小さなエンジンです。MoE(専門家混合)モデルや Gated DeltaNet 系のハイブリッドモデル、
21
+ 画像入力にも対応し、**OpenAI 互換 API サーバー**とブラウザのチャット画面を備えています。
22
+ llama.cpp と GGUF の関係と同じく、エンジンとモデルは別々にダウンロードします。エンジンは一つ、コマンドも一つで、
23
+ 変換済みのモデルならどれでも動きます。
24
+
25
+ ```bash
26
+ hf download ryugyosoft/onw --local-dir onw && cd onw
27
+ setup.bat # Windows:タスクトレイ常駐アプリ(おすすめ)
28
+ bash setup.sh # Ubuntu:右上に常駐するアプリ
29
+ start.bat # サーバーを一度だけ起動する場合(既定モデル:LFM2-8B-A1B)
30
+ bash start.sh ryugyosoft/Qwen3.6-35B-A3B-onw # Ubuntu で別のモデルを起動
31
+ ```
32
+
33
+ `setup` は Python 3.10 以上を探し(Windows で見つからない場合は winget で Python 3.12 を入れるか確認します)、
34
+ `.venv` にエンジンと `onw` コマンドを入れ、スタートメニュー/アプリ一覧に **onw** を登録して常駐アプリを起動します
35
+ (Windows はタスクトレイ、Ubuntu は右上のインジケーター)。この時点ではモデルはまだ入っていません。
36
+ 開いた **onw ウィンドウ**の「モデル」タブで、サイズ・必要メモリ・この PC で足りるかを見てモデルを選び、
37
+ 「ダウンロード」を押します。あとは「サーバー」タブから起動します。モデルの初回起動時だけ NPU 向けのコンパイルが
38
+ 走り、2 回目以降は約 20 秒で起動します。「設定」タブでは、ポート、LAN からの接続、API キー、コンテキスト長、デバイス、
39
+ 先読み検証(PLD)、64 トークン処理、ログイン時の自動起動、モデルの保存先を変えられます。
40
+
41
+ **サーバーは OpenAI 互換 API です。** 他のアプリからは base URL に `http://localhost:8000/v1`、モデル名にウィンドウに
42
+ 表示される名前を指定します。`GET /` でサーバーの案内(JSON)、`/health` でモデルの読み込み完了を確認できます。
43
+ CORS は開放済みで、LAN に公開する場合は API キー(`--api-key` または設定画面)で `/v1` を保護できます。
44
+ 動作確認用に、チャット画面(`/chat`)と、テキスト・先読み検証・2 ターン会話・画像の検証を一括実行して速度を表に
45
+ する検証ページ(`/check`)もあり、どちらもトレイのメニューとウィンドウから開けます。
46
+ NPU ドライバーがない環境では CPU で動きます。
47
+
48
+ ## `onw` コマンド
49
+
50
+ ```bash
51
+ onw serve MODEL [--port 8000] [--host 0.0.0.0] [--api-key KEY] [--context 4096] [--device NPU|CPU] [--no-pld] [--open]
52
+ onw chat MODEL [--think] [--context 4096] # ターミナルでチャット
53
+ onw tray # 常駐アプリ(setup が起動するもの)
54
+ onw window [--tab models] # モデル管理・サーバー操作・設定のウィンドウ
55
+ onw convert HF_DIR OUT_DIR [--compact] [--prune SAL.json --keep 0.5] [--graphs-only]
56
+ onw list # 対応アーキテクチャ
57
+ ```
58
+
59
+ 設定を変えたときに何が必要か:
60
+
61
+ | 設定 | 変え方 | 再コンパイル |
62
+ |---|---|---|
63
+ | temperature、top_p、top_k、繰り返し抑制(repetition / presence / frequency penalty)、max_tokens、stop、seed、思考モード | リクエストごと(API またはチャット画面) | 不要 |
64
+ | ポート、待ち受けアドレス、モデル、デバイス、先読み検証のオン/オフ | サーバーのオプション/設定画面、再起動(約 20 秒) | 不要 |
65
+ | **コンテキスト長**(`--context`) | サーバーのオプション/設定画面 | **長さごとに 1 回**(入力の形が変わるだけで、モデルファイルはそのまま) |
66
+ | 64 トークン処理(Gemma) | `ONW_S64=1/0`/設定画面 | 初回のみ |
67
+ | ブロックサイズ、画像サイズ、量子化 | `onw convert` | 必要(変換し直し) |
68
+
69
+ コンテキスト長を長くすると、短い会話でも 1 トークンごとの計算とメモリが増えます(固定長の KV キャッシュ全体を
70
+ ��回参照するため)。モデルの最大値ではなく、必要な長さを選んでください。
71
+
72
+ `MODEL` には onw 形式のモデルフォルダか Hugging Face のリポジトリ ID を指定します(ID の場合は `./models` に
73
+ ダウンロードされ、途中から再開できます)。`onw` コマンドは初回の `setup`/`start` のあと `.venv` に入ります
74
+ (Windows は `.venv\Scripts\onw`、Linux は `source .venv/bin/activate`)。任意の環境に入れる場合は
75
+ `pip install --pre -e . --extra-index-url https://storage.openvinotoolkit.org/simple/wheels/nightly` です。
76
+
77
+ Git LFS への対策:Git LFS なしで `git clone` したモデルには、重みの代わりに約 130 バイトの「ポインタ」ファイルしか
78
+ 入っていません。`onw` はこれを検出し、直し方を表示して止まります。`hf download` を使うか、リポジトリ ID をそのまま
79
+ 渡してください。
80
+
81
+ ## モデル
82
+
83
+ | onw モデル | アーキテクチャ | 入力 | NPU 3720 生成速度 | ダウンロード | 使用メモリ |
84
+ |---|---|---|---|---|---|
85
+ | [ryugyosoft/LFM2-8B-A1B-onw](https://huggingface.co/ryugyosoft/LFM2-8B-A1B-onw) | `lfm2_moe`(短い畳み込み+GQA+MoE 32 個から 4 個) | テキスト | 16〜17 tok/s(コード修正・要約では先読み検証で 29〜43) | 4.0 GB | 約 6 GB |
86
+ | [ryugyosoft/gemma-4-E4B-it-onw](https://huggingface.co/ryugyosoft/gemma-4-E4B-it-onw) | `gemma4`(検証済みの Gemma NPU グラフを取り込み) | テキスト+画像 | 7.2 tok/s | 4.3 GB | 約 15 GB |
87
+ | [ryugyosoft/Qwen3.5-9B-onw](https://huggingface.co/ryugyosoft/Qwen3.5-9B-onw) | `qwen3_5`(Gated DeltaNet+ゲート付きアテンション、dense) | テキスト+画像 | 4.1 tok/s | 5.3 GB | 約 20 GB |
88
+ | [ryugyosoft/Qwen3.6-REAP-18B-A3B-onw](https://huggingface.co/ryugyosoft/Qwen3.6-REAP-18B-A3B-onw) | `qwen3_5_moe`、256 個中 128 個のエキスパートを残す(REAP) | テキスト+画像 | 6.8 tok/s | 9.6 GB | 約 15 GB |
89
+ | [ryugyosoft/Qwen3.6-35B-A3B-onw](https://huggingface.co/ryugyosoft/Qwen3.6-35B-A3B-onw) | `qwen3_5_moe`(Gated DeltaNet+ゲート付きアテンション+MoE 256 個から 8 個+共有エキスパート) | テキスト+画像 | 6.8 tok/s | 17 GB | 約 23 GB |
90
+
91
+ 使用メモリは、テスト機(Core Ultra 9 285HX)で読み込んだあとのプロセスの作業セットです(OS が必要に応じて手放せる、
92
+ メモリマップされた重みファイル分を含みます)。先読み検証は、16 トークンの検証ブロックが十分安く済むモデル
93
+ (LFM2、Qwen3.5-9B、Gemma)で自動的に使われます。どのモデルも会話の状態をターン間で保持するので、画像付きの会話でも
94
+ 2 ターン目は新しいメッセージだけを処理します(約 2 秒)。
95
+
96
+ ## モデルを小さくする(onw convert --compact / --prune)
97
+
98
+ - `--compact`:1 トークン用と 16/64 トークン用のグラフが同じ重みファイルを共有します(ダウンロード量・ディスク)。
99
+ ホスト側で引くだけの表(出力層と共有していない埋め込み、Gemma の層ごとの埋め込み)は 32 要素ごとのスケール付き
100
+ INT4 に、Qwen の画像エンコーダーは INT8 にします。これらは使用メモリも減らしますが、重みファイルの共有だけは
101
+ ディスクにしか効きません(NPU はブロックサイズごとにコンパイル済みの重みを持つため)。
102
+ - `--prune SAL.json --keep 0.5`(MoE):REAP によるエキスパートの刈り込み。`python -m onw.prune calibrate HF_DIR SAL.json`
103
+ で各エキスパートの寄与度(日本語・英語・コードのチャットデータでの「ルーターの重み×出力の大きさ」の平均)を測り、
104
+ `python -m onw.prune eval` で残す割合ごとのパープレキシティを確認できます。Qwen3.6-35B-A3B は 256 個中 128 個を
105
+ 残してパープレキシティ +8.9%(Qwen3.6-REAP-18B-A3B-onw として公開)。エキスパートが 32 個しかない LFM2 は
106
+ 半分で +35% と落ち方が大きいため、刈り込んでいません。
107
+
108
+ ## MoE を NPU で動かす仕組み
109
+
110
+ NPU は形の決まったグラフしか扱えませんが、MoE はトークンごとに違うエキスパートを選びます。NPU 3720 で Qwen3.6 の
111
+ 寸法の 1 層・1 トークンを測ると、全エキスパートを計算すると 45 ms、グラフ内の Gather で選ぶ方式は INT4 定数では
112
+ コンパイルできず、INT8 や詰め込み形式でも 18〜135 ms(NPU が表全体を転送するため)でした。
113
+ **選んだエキスパートを実行時の入力として渡す方式なら 1.7 ms** で、NPU のメモリ帯域いっぱいに出ます。そこで:
114
+
115
+ - ネットワークを **MoE のルーターの直後で区間に分け**、ルーターの上位 k 個をホストが受け取って、次の区間に選ばれた
116
+ エキスパートの��みバッファを結び付けます(`set_tensor`、コピーなし。全エキスパートは NPU から見えるホストメモリ上の
117
+ `experts.bin` にあります)。
118
+ - エキスパートは **チャネル単位 INT4 を 1 エキスパート 1 テンソルに詰めた形** にします。実行時に渡す重みを NPU が
119
+ 全速で扱えるのはこの形だけです(グループ単位のスケールを付けると要素ごとの遅い処理になり、20 倍遅くなります)。
120
+ - それ以外は区間の中のグループ 128 の INT4 定数です。出力層は共有の INT8 入力として独立した区間に置き(ブロック
121
+ サイズが違っても 1 つだけ、ロジットが不要なプロンプト処理では飛ばす)、埋め込みと Gemma の 2.7 GB の層ごとの
122
+ 埋め込みはホストで引きます(実行時入力からの Gather は NPU が表全体を転送するため)。
123
+ - プロンプトのブロックではブロック内の全トークンが選んだエキスパートの和集合を、生成時はそのトークンの上位 k 個を
124
+ 結び付けます。
125
+
126
+ ## エンジンの構成
127
+
128
+ | ファイル | 内容 |
129
+ |---|---|
130
+ | `onw/ir.py` | グラフ部品:量子化線形層、RMSNorm(FP16 で溢れない前処理つき)、RoPE/部分 RoPE、ホスト管理の KV キャッシュ上のアテンション、エキスパート枠、Gated DeltaNet(1 トークン用の行列形式、ブロック倍々の厳密な逆行列によるチャンク並列形式) |
131
+ | `onw/runtime.py` | 区間の実行、命名規則による状態管理(KV の行、畳み込み窓、再帰状態)、エキスパートの結び付け、MRoPE、スライディング窓マスク、ホスト側の表引き、先読み検証のための正確な巻き戻し |
132
+ | `onw/chat.py` | 生成ループ:画像(Qwen/Gemma)、サンプリング、停止文字列、先読み検証(正確な巻き戻し、再帰状態を持つモデルでは遅延やり直し、効く場合だけ自動で有効)、ターン間の差分処理 |
133
+ | `onw/models/` | アーキテクチャごとの組み立て定義:`lfm2_moe`、`qwen3_5_moe`(dense と MoE)、`qwen_vision`、`gemma4` |
134
+ | `onw/hub.py`、`onw/server.py`、`onw/web/` | モデルのダウンロードと LFS 対策、OpenAI 互換サーバー、チャット画面(`/chat`)、検証ページ(`/check`) |
135
+ | `onw/app.py`、`onw/tray.py`、`setup.*`、`find_python.bat` | onw ウィンドウ(モデル/サーバー/設定)、トレイアイコン、インストーラー(Python の検出と winget) |
136
+ | `check_ref.py`、`check_vision.py`、`test_chat.py`、`test_image.py`、`probe_segments.py`、`prof_*.py` | 元の HF モデルとの比較、生成・画像・複数ターンの検証、区間ごとの NPU と CPU の比較、速度計測 |
137
+
138
+ ## 注意点と制限
139
+
140
+ - コンテキスト長の既定は 1024 トークンです。`--context`/設定画面で読み込み時に変えられます(長さごとに NPU 向け
141
+ コンパイルが 1 回走ります)。Qwen の画像は 512×512(256 トークン)に縮小し、Gemma の画像は縦横比を保ちます
142
+ (最大 280 トークン)。
143
+ - エキスパートはチャネル単位 INT4(四捨五入量子化)です。確認した範囲では、最初のトークンのロジットは bf16 との差が
144
+ 約 33% で、1 位の予測は一致しました。回答は自然ですが、数トークン先から言い回しが bf16 モデルと分かれます。
145
+ - 16 トークンより大きいプロンプトブロック(Gemma の 64 トークン版)は RAM 24 GB 以上のときだけ読み込みます
146
+ (`ONW_S64=1/0` で強制)。
147
+ - OpenVINO のプレリリース版(2026.5 nightly)が必要です。NPU 3720(Windows 11)で動作確認済み、CPU での動作は
148
+ Ubuntu 22.04(WSL)で確認しています。NPU 4000(Lunar Lake)と Ubuntu の右上常駐は、このエンジンではまだ未確認です。
149
+
150
+ ## ライセンス
151
+
152
+ コードは Apache 2.0。変換済みモデルは元のモデルのライセンスに従います(各モデルのリポジトリを参照)。
README_en.md ADDED
@@ -0,0 +1,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # onw — 俺のNPUがこんなに動くわけない
2
+
3
+ [日本語](README.md) | **English**
4
+
5
+ *(Ore no NPU ga konna ni ugoku wake nai — "There's no way my NPU runs this well")*
6
+
7
+ A small engine that runs LLMs — including MoE and hybrid DeltaNet models, with image input — **entirely on the
8
+ Intel NPU**, with an OpenAI-compatible server and a browser chat UI. Engine and models are separate downloads,
9
+ like llama.cpp + a GGUF: one engine, one command, any converted model.
10
+
11
+ ```bash
12
+ hf download ryugyosoft/onw --local-dir onw && cd onw
13
+ setup.bat # Windows: resident task-tray app (recommended)
14
+ bash setup.sh # Ubuntu: resident top-bar app
15
+ start.bat # or just run the server once (default model: LFM2-8B-A1B)
16
+ bash start.sh ryugyosoft/Qwen3.6-35B-A3B-onw # Ubuntu, another model
17
+ ```
18
+
19
+ `setup` finds Python 3.10+ (on Windows it offers to install Python 3.12 with winget when there is none),
20
+ installs the engine and the `onw` command into `.venv`, adds **onw** to the Start menu / application list and
21
+ starts a small resident app (task tray on Windows, top-bar indicator on Ubuntu). No model is downloaded yet:
22
+ the **onw window** opens on its *Models* tab - pick a model (sizes, memory needs and what fits this PC are shown)
23
+ and press *Download*. Then the *Server* tab starts it; the first start of a model compiles it for the NPU, later
24
+ starts take ~20 s. The *Settings* tab has port, LAN access, API key, context length, device, prompt lookup
25
+ decoding, 64-token blocks, start at login and the models folder.
26
+
27
+ **The server is an OpenAI-compatible API**: point other apps at `http://localhost:8000/v1` with the model name
28
+ shown in the window (`GET /` describes the server, `/health` answers once the model is loaded, CORS is open,
29
+ `--api-key` / the settings' API key protects `/v1`). For trying things out it also serves a chat UI at `/chat` and
30
+ a check page at `/check` that runs a text, prompt-lookup, two-turn and image test and shows the speeds; both open
31
+ from the tray menu and the window. Without an NPU driver everything runs on the CPU.
32
+
33
+ ## The `onw` command
34
+
35
+ ```bash
36
+ onw serve MODEL [--port 8000] [--host 0.0.0.0] [--api-key KEY] [--context 4096] [--device NPU|CPU] [--no-pld] [--open]
37
+ onw chat MODEL [--think] [--context 4096] # chat in the terminal
38
+ onw tray # the resident app (what setup starts)
39
+ onw window [--tab models] # model manager, server control, settings
40
+ onw convert HF_DIR OUT_DIR [--compact] [--prune SAL.json --keep 0.5] [--graphs-only]
41
+ onw list # supported architectures
42
+ ```
43
+
44
+ What needs what:
45
+
46
+ | setting | how | recompile? |
47
+ |---|---|---|
48
+ | temperature, top_p, top_k, repetition / presence / frequency penalty, max_tokens, stop, seed, thinking | per request (API or chat UI) | no |
49
+ | port, address, model, device, prompt lookup on/off | server option / tray settings, restart (~20 s) | no |
50
+ | **context length** (`--context`) | server option / tray settings | **once per length** (input shapes change; the model files stay the same) |
51
+ | 64-token prompt blocks (Gemma) | `ONW_S64=1/0` / tray setting | once, the first time |
52
+ | block sizes, image size, quantization | `onw convert` | yes (new conversion) |
53
+
54
+ A longer context costs memory and time on every token (the static KV cache is attended in full), so pick what you
55
+ need rather than the model's maximum.
56
+
57
+ `MODEL` is an onw model folder or a Hugging Face repo id (downloaded into `./models`, resumable). The command is in
58
+ `.venv` after the first `start` (`.venv\Scripts\onw` on Windows, `source .venv/bin/activate` on Linux), or
59
+ install it anywhere with `pip install --pre -e . --extra-index-url https://storage.openvinotoolkit.org/simple/wheels/nightly`.
60
+
61
+ Git LFS guard: a model repo cloned with `git` but without Git LFS contains ~130-byte pointer files instead of the
62
+ weights; `onw` detects that and stops with instructions. Prefer `hf download` (or just pass the repo id).
63
+
64
+ ## Models
65
+
66
+ | onw model | arch | input | NPU 3720 decode | download | memory in use |
67
+ |---|---|---|---|---|---|
68
+ | [ryugyosoft/LFM2-8B-A1B-onw](https://huggingface.co/ryugyosoft/LFM2-8B-A1B-onw) | `lfm2_moe` (short conv + GQA + MoE 32x top-4) | text | 16-17 tok/s (29-43 with prompt lookup on code / summaries) | 4.0 GB | ~6 GB |
69
+ | [ryugyosoft/gemma-4-E4B-it-onw](https://huggingface.co/ryugyosoft/gemma-4-E4B-it-onw) | `gemma4` (imported from the validated Gemma NPU graphs) | text + image | 7.2 tok/s | 4.3 GB | ~15 GB |
70
+ | [ryugyosoft/Qwen3.5-9B-onw](https://huggingface.co/ryugyosoft/Qwen3.5-9B-onw) | `qwen3_5` (Gated DeltaNet + gated attention, dense) | text + image | 4.1 tok/s | 5.3 GB | ~20 GB |
71
+ | [ryugyosoft/Qwen3.6-REAP-18B-A3B-onw](https://huggingface.co/ryugyosoft/Qwen3.6-REAP-18B-A3B-onw) | `qwen3_5_moe`, 128 of 256 experts kept (REAP) | text + image | 6.8 tok/s | 9.6 GB | ~15 GB |
72
+ | [ryugyosoft/Qwen3.6-35B-A3B-onw](https://huggingface.co/ryugyosoft/Qwen3.6-35B-A3B-onw) | `qwen3_5_moe` (Gated DeltaNet + gated attention + MoE 256x top-8 + shared expert) | text + image | 6.8 tok/s | 17 GB | ~23 GB |
73
+
74
+ Memory is the process working set on the test machine after loading (includes memory-mapped weight files the
75
+ OS can page out).
76
+
77
+ Measured with an image chat / text prompts on Core Ultra 9 285HX; prompt lookup decoding is used automatically
78
+ where a 16-token verification block is cheap enough (LFM2, Qwen3.5-9B, Gemma), and every model keeps the
79
+ conversation state between turns (a follow-up with an image re-processes only the new message: ~2 s).
80
+
81
+ ## Smaller models (onw convert --compact / --prune)
82
+
83
+ - `--compact`: the 1-token and 16/64-token graphs of a segment share one weights file (download / disk);
84
+ host-only lookup tables (token embeddings not tied to the LM head, Gemma's per-layer embeddings) are INT4 with a
85
+ scale per 32 values; the Qwen vision tower is INT8. These also shrink the memory in use, except the shared
86
+ weights file: the NPU keeps one compiled copy per block size.
87
+ - `--prune SAL.json --keep 0.5` (MoE): REAP expert pruning. `python -m onw.prune calibrate HF_DIR SAL.json`
88
+ measures each expert's saliency (mean router weight x output norm over chat data in Japanese, English and code),
89
+ `python -m onw.prune eval` reports the perplexity for several keep ratios. Qwen3.6-35B-A3B keeps 128 of 256
90
+ experts at +8.9% perplexity (published as Qwen3.6-REAP-18B-A3B-onw); LFM2 with only 32 experts loses +35% at
91
+ 50%, so it is not pruned.
92
+
93
+ ## How it runs a MoE on an NPU
94
+
95
+ NPUs need static graphs, and a sparse MoE picks different experts per token. Measured on NPU 3720 (Qwen3.6
96
+ sizes, per layer, one token): computing every expert 45 ms; selecting experts inside the graph with Gather does
97
+ not compile for INT4 constants and is 18-135 ms for INT8 / packed forms (the NPU moves whole tables);
98
+ **passing the chosen experts as runtime inputs: 1.7 ms**, at the NPU's memory bandwidth. So:
99
+
100
+ - the network is cut into **segments right after every MoE router**; the router's top-k goes to the host, which
101
+ binds the selected experts' weight buffers into the next segment (`set_tensor`, zero copy: all experts live in
102
+ NPU-visible host memory, `experts.bin`);
103
+ - experts are **channel-wise INT4 packed as one tensor per expert** — the only weights-as-inputs form the NPU runs
104
+ at full speed (group-wise scales on runtime weights fall back to slow element-wise work, 20x slower);
105
+ - everything else is group-128 INT4 constants inside the segments; the LM head is a shared INT8 input in its own
106
+ segment (one copy for all block sizes, skipped for prompt blocks that need no logits); token embeddings and
107
+ Gemma's 2.7 GB per-layer embeddings are looked up on the host (a gather from a runtime input makes the NPU move
108
+ the whole table);
109
+ - prompt blocks bind the union of the block's experts; decoding binds the token's top-k.
110
+
111
+ ## Engine pieces
112
+
113
+ | file | what |
114
+ |---|---|
115
+ | `onw/ir.py` | graph blocks: quantized linears, RMSNorm (with an fp16-safe prescale), RoPE / partial RoPE, attention over a host KV cache, expert slots, Gated DeltaNet (1-token matrix form; chunkwise-parallel form with an exact block-doubling inverse) |
116
+ | `onw/runtime.py` | runs the segments; state by naming convention (KV rows, conv windows, recurrent states), expert binding, MRoPE, sliding-window masks, host table lookups, exact truncation for speculative verification |
117
+ | `onw/chat.py` | chat loop: images (Qwen / Gemma), sampling, stop strings, prompt lookup decoding (exact rewind, or lazy redo for recurrent models, switched on only when it pays), prefix reuse between turns |
118
+ | `onw/models/` | one builder per architecture: `lfm2_moe`, `qwen3_5_moe` (dense and MoE), `qwen_vision`, `gemma4` |
119
+ | `onw/hub.py`, `onw/server.py`, `onw/web/` | model download + LFS guard, OpenAI-compatible server, chat UI (`/chat`), check page (`/check`) |
120
+ | `onw/app.py`, `onw/tray.py`, `setup.*`, `find_python.bat` | onw window (models / server / settings), tray icon, installers (Python detection + winget) |
121
+ | `check_ref.py`, `check_vision.py`, `test_chat.py`, `test_image.py`, `probe_segments.py`, `prof_*.py` | vs the HF model, generation / image / multi-turn checks, NPU-vs-CPU per segment, timing |
122
+
123
+ ## Notes and limitations
124
+
125
+ - The context is 1024 tokens by default; `--context` / the settings change it at load time (one NPU compile per
126
+ length). Qwen images are resized to 512x512 (256 tokens); Gemma images keep their aspect
127
+ ratio (up to 280 tokens).
128
+ - Experts are channel-wise INT4 (round-to-nearest): first-token logits within ~33% of bf16 with the same top-1 on
129
+ our checks; answers are fluent, word choices diverge from the bf16 model after a few tokens.
130
+ - Prompt blocks above 16 tokens (Gemma's 64-token block) are loaded only with >= 24 GB RAM (`ONW_S64=1/0`).
131
+ - Needs a pre-release OpenVINO (2026.5 nightly). Tested on NPU 3720 (Windows 11); CPU fallback on Ubuntu 22.04
132
+ (WSL). NPU 4000 (Lunar Lake) untested with this engine.
133
+
134
+ ## License
135
+
136
+ Code: Apache 2.0. Converted models carry their base model's license (see each model repo).
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__ = "0.4.0"
 
2
 
3
  A small engine that runs LLMs entirely on the Intel NPU: static segment graphs + host-managed state.
4
  """
5
+ __version__ = "0.4.1"
pyproject.toml CHANGED
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
 
5
  [project]
6
  name = "onw"
7
- version = "0.4.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"}
 
4
 
5
  [project]
6
  name = "onw"
7
+ version = "0.4.1"
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"}
setup.bat CHANGED
@@ -1,44 +1,44 @@
1
- @echo off
2
- rem onw - Ore no NPU ga konna ni ugoku wake nai: install the engine and the resident tray app (Windows).
3
- rem Finds Python 3.10+ (offers to install it with winget), creates .venv, installs onw + the tray, adds "onw" to
4
- rem the Start menu and starts it in the task tray. Models are downloaded later from the onw window (Models tab).
5
- setlocal
6
- cd /d "%~dp0"
7
- set PYTHONUTF8=1
8
-
9
- if exist ".venv\installed-tray.txt" goto shortcut
10
- if exist ".venv\Scripts\python.exe" goto install
11
- call find_python.bat
12
- if not defined PY (
13
- pause
14
- exit /b 1
15
- )
16
- echo [setup] creating Python environment in .venv ...
17
- %PY% -m venv .venv
18
- if errorlevel 1 (
19
- echo [setup] could not create the Python environment.
20
- pause
21
- exit /b 1
22
- )
23
- :install
24
- ".venv\Scripts\python.exe" -m pip install -U pip
25
- ".venv\Scripts\python.exe" -m pip install --pre -e .[tray] --extra-index-url https://storage.openvinotoolkit.org/simple/wheels/nightly
26
- if errorlevel 1 (
27
- echo [setup] package installation failed.
28
- pause
29
- exit /b 1
30
- )
31
- echo ok> ".venv\installed.txt"
32
- echo ok> ".venv\installed-tray.txt"
33
-
34
- :shortcut
35
- rem Start menu entry: pythonw -m onw tray (no console window)
36
- set "PYW=%CD%\.venv\Scripts\pythonw.exe"
37
- powershell -NoProfile -Command ^
38
- "$s = (New-Object -ComObject WScript.Shell).CreateShortcut([Environment]::GetFolderPath('Programs') + '\onw.lnk');" ^
39
- "$s.TargetPath = '%PYW%'; $s.Arguments = '-m onw tray'; $s.WorkingDirectory = '%CD%';" ^
40
- "$s.Description = 'onw - LLMs on the Intel NPU'; $s.Save()"
41
- echo [setup] Start menu: onw
42
-
43
- echo [start] onw is starting in the task tray. Pick a model to download in the window that opens.
44
- start "" "%PYW%" -m onw tray
 
1
+ @echo off
2
+ rem onw - Ore no NPU ga konna ni ugoku wake nai: install the engine and the resident tray app (Windows).
3
+ rem Finds Python 3.10+ (offers to install it with winget), creates .venv, installs onw + the tray, adds "onw" to
4
+ rem the Start menu and starts it in the task tray. Models are downloaded later from the onw window (Models tab).
5
+ setlocal
6
+ cd /d "%~dp0"
7
+ set PYTHONUTF8=1
8
+
9
+ if exist ".venv\installed-tray.txt" goto shortcut
10
+ if exist ".venv\Scripts\python.exe" goto install
11
+ call "%~dp0find_python.bat"
12
+ if not defined PY (
13
+ pause
14
+ exit /b 1
15
+ )
16
+ echo [setup] creating Python environment in .venv ...
17
+ %PY% -m venv .venv
18
+ if errorlevel 1 (
19
+ echo [setup] could not create the Python environment.
20
+ pause
21
+ exit /b 1
22
+ )
23
+ :install
24
+ ".venv\Scripts\python.exe" -m pip install -U pip
25
+ ".venv\Scripts\python.exe" -m pip install --pre -e .[tray] --extra-index-url https://storage.openvinotoolkit.org/simple/wheels/nightly
26
+ if errorlevel 1 (
27
+ echo [setup] package installation failed.
28
+ pause
29
+ exit /b 1
30
+ )
31
+ echo ok> ".venv\installed.txt"
32
+ echo ok> ".venv\installed-tray.txt"
33
+
34
+ :shortcut
35
+ rem Start menu entry: pythonw -m onw tray (no console window)
36
+ set "PYW=%CD%\.venv\Scripts\pythonw.exe"
37
+ powershell -NoProfile -Command ^
38
+ "$s = (New-Object -ComObject WScript.Shell).CreateShortcut([Environment]::GetFolderPath('Programs') + '\onw.lnk');" ^
39
+ "$s.TargetPath = '%PYW%'; $s.Arguments = '-m onw tray'; $s.WorkingDirectory = '%CD%';" ^
40
+ "$s.Description = 'onw - LLMs on the Intel NPU'; $s.Save()"
41
+ echo [setup] Start menu: onw
42
+
43
+ echo [start] onw is starting in the task tray. Pick a model to download in the window that opens.
44
+ start "" "%PYW%" -m onw tray
start.bat CHANGED
@@ -1,52 +1,52 @@
1
- @echo off
2
- rem onw - Ore no NPU ga konna ni ugoku wake nai: run the server once from a console (Windows).
3
- rem start.bat LFM2-8B-A1B (default, fits 16 GB machines)
4
- rem start.bat ryugyosoft/Qwen3.6-35B-A3B-onw any onw model: a Hugging Face repo id or a local folder
5
- rem start.bat MODEL --port 8080 further options go to "onw serve"
6
- rem First run: finds / installs Python, creates .venv (~1 GB), downloads the model, compiles it for the NPU.
7
- rem For the resident tray app with a model manager use setup.bat instead.
8
- setlocal
9
- cd /d "%~dp0"
10
- set PYTHONUTF8=1
11
- set "MODEL=ryugyosoft/LFM2-8B-A1B-onw"
12
- set "FIRST=%~1"
13
- if not "%FIRST%"=="" if not "%FIRST:~0,1%"=="-" (
14
- set "MODEL=%FIRST%"
15
- shift
16
- )
17
-
18
- if exist ".venv\installed.txt" goto args
19
- if exist ".venv\Scripts\python.exe" goto install
20
- call find_python.bat
21
- if not defined PY (
22
- pause
23
- exit /b 1
24
- )
25
- echo [setup] creating Python environment in .venv ...
26
- %PY% -m venv .venv
27
- if errorlevel 1 (
28
- echo [setup] could not create the Python environment.
29
- pause
30
- exit /b 1
31
- )
32
- :install
33
- ".venv\Scripts\python.exe" -m pip install -U pip
34
- ".venv\Scripts\python.exe" -m pip install --pre -e . --extra-index-url https://storage.openvinotoolkit.org/simple/wheels/nightly
35
- if errorlevel 1 (
36
- echo [setup] package installation failed.
37
- pause
38
- exit /b 1
39
- )
40
- echo ok> ".venv\installed.txt"
41
-
42
- :args
43
- set "ARGS="
44
- :collect
45
- if "%~1"=="" goto run
46
- set "ARGS=%ARGS% %1"
47
- shift
48
- goto collect
49
- :run
50
- echo [start] %MODEL% (first time: download + NPU compile, please wait) ...
51
- ".venv\Scripts\onw.exe" serve %MODEL% --open %ARGS%
52
- pause
 
1
+ @echo off
2
+ rem onw - Ore no NPU ga konna ni ugoku wake nai: run the server once from a console (Windows).
3
+ rem start.bat LFM2-8B-A1B (default, fits 16 GB machines)
4
+ rem start.bat ryugyosoft/Qwen3.6-35B-A3B-onw any onw model: a Hugging Face repo id or a local folder
5
+ rem start.bat MODEL --port 8080 further options go to "onw serve"
6
+ rem First run: finds / installs Python, creates .venv (~1 GB), downloads the model, compiles it for the NPU.
7
+ rem For the resident tray app with a model manager use setup.bat instead.
8
+ setlocal
9
+ cd /d "%~dp0"
10
+ set PYTHONUTF8=1
11
+ set "MODEL=ryugyosoft/LFM2-8B-A1B-onw"
12
+ set "FIRST=%~1"
13
+ if not "%FIRST%"=="" if not "%FIRST:~0,1%"=="-" (
14
+ set "MODEL=%FIRST%"
15
+ shift
16
+ )
17
+
18
+ if exist ".venv\installed.txt" goto args
19
+ if exist ".venv\Scripts\python.exe" goto install
20
+ call "%~dp0find_python.bat"
21
+ if not defined PY (
22
+ pause
23
+ exit /b 1
24
+ )
25
+ echo [setup] creating Python environment in .venv ...
26
+ %PY% -m venv .venv
27
+ if errorlevel 1 (
28
+ echo [setup] could not create the Python environment.
29
+ pause
30
+ exit /b 1
31
+ )
32
+ :install
33
+ ".venv\Scripts\python.exe" -m pip install -U pip
34
+ ".venv\Scripts\python.exe" -m pip install --pre -e . --extra-index-url https://storage.openvinotoolkit.org/simple/wheels/nightly
35
+ if errorlevel 1 (
36
+ echo [setup] package installation failed.
37
+ pause
38
+ exit /b 1
39
+ )
40
+ echo ok> ".venv\installed.txt"
41
+
42
+ :args
43
+ set "ARGS="
44
+ :collect
45
+ if "%~1"=="" goto run
46
+ set "ARGS=%ARGS% %1"
47
+ shift
48
+ goto collect
49
+ :run
50
+ echo [start] %MODEL% (first time: download + NPU compile, please wait) ...
51
+ ".venv\Scripts\onw.exe" serve %MODEL% --open %ARGS%
52
+ pause