Intel® NPU Acceleration Library を使って LocalLLM を Intel NPU で動かす

# はじめに

Core Ultra 7 155H 搭載のノパソを買いました。 Core Ultra には NPU (Neural network Processing Unit) がついていて、AIの処理がつよつよになっています。そこで、最近自分の周りで流行っている LocalLLM を NPU を使って動かしました。本記事はその記録です。

# やったこと

* NPU ドライバの更新
* rye で intel_npu_acceleration_library を追加して実行
    * numpy はバージョン 2 未満を入れる
* ライブラリで動かない部分を手動で書き換えて直す
* `Qiita.com is` という文章の続きをサンプルプログラムを使って生成してもらって NPU が動いてるのを確認

# 実行環境

* OS: Windows11
* CPU: Core Ultra 7 155H
* メモリ: 32GB
* Intel® NPU Acceleration Library: v1.3.0

# Intel® NPU Acceleration Library

Intel® NPU Acceleration Library は Intel NPU を使うための Python ライブラリです。[github](https://github.com/intel/intel-npu-acceleration-library)
2024年8月1日現在、開発途中です。

# NPU ドライバの更新

NPU ドライバを最新のにする必要があるのですが、デバイスマネージャーから自動でやっても最新のにならないので、手動でやります。2024年8月1日で、自分のパソコンにはバージョン31.0.100.1688が入っていて、最新は32.0.100.2540だったので、それを[リリースノート](https://downloadmirror.intel.com/825735/NPU_Win_Release_Notes_v2540.pdf)の手順で入れてきます。

[ドライバのダウンロードページ](https://www.intel.com/content/www/us/en/download/794734/intel-npu-driver-windows.html)から Zip ファイルをダウンロード、解答します。

デバイスマネージャーで Neural processors -> Intel(R) AI Boost を右クリックしてアンインストールします。`このデバイスのドライバーを削除しようとしました。` にチェックを入れてデバイスのアンインストールをしてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3474188/54a8a6e3-0989-883f-54bc-d196e7713587.png)

上の方にあるこのアイコン ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3474188/77f0bef4-8235-8c8f-18d2-9987c8c50746.png) (ハードウェア更新のスキャン)をクリックして ほかのデバイス -> PCIデバイス を右クリックして コンピューターを参照してドライバーを検索(下の方) をクリックしてください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3474188/114d987d-eb1b-dabb-801b-7705709daf90.png)

回答したドライバーのフォルダを参照して、`サブフォルダーを検索する` にチェックを入れてください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3474188/7ca2eb4b-6060-7eb0-db8e-8aad9463cba6.png)

Intel(R) AI Boost のプロパティを見てバージョンが最新のになってれば OK です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3474188/1bed5109-caf5-c918-d444-566c7920f1a5.png)

# Python 環境構築

Python のパッケージ管理システムは [rye](https://rye.astral.sh/) を使います。 numpy は 2 より前のにします。

```
rye init
rye add intel_npu_acceleration_library
rye add numpy<2
rye sync
```

# 実行

`src` のなかに `main.py` を作り、Github にある Tiny-llama を動かすサンプルプログラムを書きます。

```python:src/main.py
from transformers import AutoTokenizer, TextStreamer
from intel_npu_acceleration_library import NPUModelForCausalLM
import torch

model_id = "TinyLlama/TinyLlama-1.1B-Chat-v1.0"

model = NPUModelForCausalLM.from_pretrained(model_id, use_cache=True, dtype=torch.int8).eval()
tokenizer = AutoTokenizer.from_pretrained(model_id, use_default_system_prompt=True)
tokenizer.pad_token_id = tokenizer.eos_token_id
streamer = TextStreamer(tokenizer, skip_special_tokens=True)


query = input("Ask something: ")
prefix = tokenizer(query, return_tensors="pt")["input_ids"]


generation_kwargs = dict(
    input_ids=prefix,
    streamer=streamer,
    do_sample=True,
    top_k=50,
    top_p=0.9,
    max_new_tokens=512,
)

print("Run inference")
_ = model.generate(**generation_kwargs)
```

実行します。

```
rye run python src/main.py
```

`Ask something:` で入力したら `TypeError: LlamaAttention.forward() got an unexpected keyword argument 'position_embeddings'` が出てきました。v1.3.0 ではこのエラーは起こるのですが、[すでに修正](https://github.com/intel/intel-npu-acceleration-library/pull/105)されてました。たぶん次のバージョンで反映されると思います。

# ライブラリの修正

とりあえずライブラリを手動で書き換えます。`.venv\Lib\site-packages\intel_npu_acceleration_library\nn\llm.py` が該当するコードです。

まずは import の追加

```diff_python
from transformers.models.llama.modeling_llama import (
    apply_rotary_pos_emb,
    repeat_kv,
    LlamaConfig,
)
from transformers import AutoTokenizer
from intel_npu_acceleration_library.nn import Linear
from intel_npu_acceleration_library.backend import run_factory, MLP
from functools import partial
- from typing import Optional, List, Generator
+ from typing import Optional, List, Generator, Tuple
from transformers.cache_utils import Cache
import torch
import uuid
```

163行目ぐらいの `LlamaAttention.forward()` に引数追加
```diff_python
def forward(
        self,
        hidden_states: torch.Tensor,
        attention_mask: Optional[torch.Tensor] = None,
        position_ids: Optional[torch.Tensor] = None,
        past_key_value: Optional[Cache] = None,
        output_attentions: Optional[bool] = False,
        use_cache: Optional[bool] = False,
        cache_position: Optional[torch.LongTensor] = None,
+       position_embeddings: Optional[Tuple[torch.Tensor, torch.Tensor]] = None,  # will become mandatory in Transformers v4.45
    ):
```

205行目付近の `cos, sin` への代入を変更

```diff_python
        query_states = query_states.view(
            bsz, q_len, self.num_heads, self.head_dim
        ).transpose(1, 2)
        key_states = key_states.view(
            bsz, q_len, self.num_key_value_heads, self.head_dim
        ).transpose(1, 2)
        value_states = value_states.view(
            bsz, q_len, self.num_key_value_heads, self.head_dim
        ).transpose(1, 2)

-       cos, sin = self.rotary_emb(value_states, position_ids)
+       if position_embeddings is None:
+           cos, sin = self.rotary_emb(value_states, position_ids)
+       else:
+           cos, sin = position_embeddings

        query_states, key_states = apply_rotary_pos_emb(
            query_states, key_states, cos, sin, position_ids
        )
```

# 実行(修正後)

実行します。

```
rye run python src/main.py
```

`Qiita.com is` の続きを生成してもらいました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3474188/403a76b0-2e3e-4771-116f-d9d6995237e3.png)

NPU も動いた！ 文章は生成できたけど、 `Qiita.com は、ダイエット、フィットネス、健康に関する情報の素晴らしいリソースです。` とか言っててダメダメじゃんw

たぶんもっといい生成モデルを使うといい感じになると思います。

# おわりに

NPU ドライバの更新をして、 rye で intel_npu_acceleration_library を追加して実行するまえにライブラリで動かない部分を手動で書き換えて直し、 `Qiita.com is` という文章の続きを生成してもらって NPU が動いてるのを確認しました。対話システム作ったり、Intel® NPU Acceleration Library が更新されたりしたらまた記事を書こうと思います。